mirror of
https://github.com/getpelican/pelican.git
synced 2025-10-15 20:28:56 +02:00
merged with upstream
This commit is contained in:
commit
f9dff82197
492 changed files with 22342 additions and 13105 deletions
3
.coveragerc
Normal file
3
.coveragerc
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[report]
|
||||
omit = pelican/tests/*
|
||||
|
||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -11,3 +11,5 @@ tags
|
|||
.tox
|
||||
.coverage
|
||||
htmlcov
|
||||
six-*.egg/
|
||||
*.orig
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
syntax: glob
|
||||
output/*
|
||||
*.pyc
|
||||
MANIFEST
|
||||
build
|
||||
dist
|
||||
docs/_build
|
||||
Paste-*
|
||||
*.egg-info
|
||||
29
.hgtags
29
.hgtags
|
|
@ -1,29 +0,0 @@
|
|||
7acafbf7e47b1287525026ad8b4f1efe443d5403 1.2
|
||||
7acafbf7e47b1287525026ad8b4f1efe443d5403 1.2
|
||||
ae850ab0fd62a98a98da7ce74ac794319c6a5066 1.2
|
||||
54a0309f79d6c5b54d8e1e3b5e3f744856b68a73 1.1
|
||||
8f5e0eb037768351eb08840e588a4364266a69b3 1.1.1
|
||||
bb986ed591734ca469f726753cbc48ebbfce0dcc 1.2.1
|
||||
8a3dad99cbfa6bb5d0ef073213d0d86e0b4c5dba 1.2.2
|
||||
4a20105a242ab154f6202aa6651979bfbb4cf95e 1.2.3
|
||||
803aa0976cca3dd737777c640722988b1f3769fe 1.2.4
|
||||
703c4511105fd9c8b85afda951a294c194e7cf3e 1.2.5
|
||||
6e46a40aaa850a979f5d09dd95d02791ec7ab0ef 2.0
|
||||
bf14d1a5c1fae9475447698f0f9b8d35c551f732 2.1
|
||||
da86343ebd543e5865050e47ecb0937755528d13 2.1.1
|
||||
760187f048bb23979402f950ecb5d3c5493995b1 2.2
|
||||
20aa16fe4daa3b70f6c063f170edc916b49837ed 2.3
|
||||
f9c1d94081504f21f5b2ba147a38099e45db1769 2.4
|
||||
e65199a0b2706d2fb48f7a3c015e869716e0bec1 2.4.1
|
||||
89dbd7b6f114508eae62fc821326f4797dfc8b23 2.4.2
|
||||
979b4473af56a191a278c83058bc9c8fa1fde30e 2.4.3
|
||||
26a444fbb78becae358afa0a5b47587db8739b21 2.4.4
|
||||
3542b65fd1963ae7065b6a3bc912fbb6c150e98c 2.4.5
|
||||
87745dfdd51b96bf18eaaf6c402effa902c1b856 2.5.0
|
||||
294a2830a393d5a97671dc211dbdb5254a15e604 2.5.1
|
||||
294a2830a393d5a97671dc211dbdb5254a15e604 2.5.1
|
||||
92b31e41134cb2c1a156ce623338cf634d2ebc3e 2.5.1
|
||||
7d728f8e771cbbc802ce81e424e08a8eecbd48dc 2.5.2
|
||||
7d728f8e771cbbc802ce81e424e08a8eecbd48dc 2.5.2
|
||||
6d368a1739a4ce48d2d04b00db04fa538e2bf90a 2.5.2
|
||||
1f9dd44b546425216b1fa35fd88d3d532da8916b 2.5.3
|
||||
24
.mailmap
Normal file
24
.mailmap
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
Alexis Métaireau <alexis@notmyidea.org>
|
||||
Alexis Métaireau <alexis@notmyidea.org> <alexis, notmyidea, org>
|
||||
Alexis Métaireau <alexis@notmyidea.org> <ametaireau@gmail.com>
|
||||
Axel Haustant <noirbizarre@gmail.com> <axel.haustant.ext@mappy.com>
|
||||
Axel Haustant <noirbizarre@gmail.com> <axel.haustant@valtech.fr>
|
||||
Dave Mankoff <mankyd@gmail.com>
|
||||
Feth Arezki <feth@tuttu.info>
|
||||
Guillaume <guillaume@lame.homelinux.com>
|
||||
Guillaume <guillaume@lame.homelinux.com> <guillaume@mint.(none)>
|
||||
Guillaume B <guitreize@gmail.com>
|
||||
Guillermo López <guilan70@hotmail.com>
|
||||
Guillermo López <guilan70@hotmail.com> <guillermo.lopez@outlook.com>
|
||||
Jomel Imperio <jimperio@gmail.com>
|
||||
Justin Mayer <entrop@gmail.com>
|
||||
Justin Mayer <entrop@gmail.com> <entroP@gmail.com>
|
||||
Marco Milanesi <kpanic@gnufunk.org> <marcom@openquake.org>
|
||||
Massimo Santini <santini@dsi.unimi.it> <santini@spillane.docenti.dsi.unimi.it>
|
||||
Rémy HUBSCHER <hubscher.remy@gmail.com> <remy.hubscher@ionyse.com>
|
||||
Simon Conseil <contact@saimon.org>
|
||||
Simon Liedtke <liedtke.simon@googlemail.com>
|
||||
Skami18 <skami@skami-laptop.dyndns.org>
|
||||
Stuart Colville <muffinresearchlabs@gmail.com> <muffinresearch@gmail.com>
|
||||
Stéphane Bunel <stephane@lutetium.(none)>
|
||||
tBunnyMan <WagThatTail@Me.com>
|
||||
20
.travis.yml
20
.travis.yml
|
|
@ -1,14 +1,22 @@
|
|||
language: python
|
||||
python:
|
||||
- "2.6"
|
||||
- "2.7"
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo locale-gen fr_FR.UTF-8 tr_TR.UTF-8
|
||||
install:
|
||||
- pip install nose unittest2 mock --use-mirrors
|
||||
- pip install . --use-mirrors
|
||||
- pip install Markdown
|
||||
script: nosetests -s tests
|
||||
- pip install .
|
||||
- pip install -r dev_requirements.txt
|
||||
- pip install nose-cov
|
||||
script: nosetests -sv --with-coverage --cover-package=pelican pelican
|
||||
after_success:
|
||||
# Report coverage results to coveralls.io
|
||||
- pip install coveralls
|
||||
- coveralls
|
||||
notifications:
|
||||
irc:
|
||||
channels:
|
||||
channels:
|
||||
- "irc.freenode.org#pelican"
|
||||
on_success: change
|
||||
|
|
|
|||
144
CONTRIBUTING.rst
Normal file
144
CONTRIBUTING.rst
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
Filing issues
|
||||
-------------
|
||||
|
||||
* Before you file an issue, try `asking for help`_ first.
|
||||
* If determined to file an issue, first check for `existing issues`_, including
|
||||
closed issues.
|
||||
|
||||
.. _`asking for help`: `How to get help`_
|
||||
.. _`existing issues`: https://github.com/getpelican/pelican/issues
|
||||
|
||||
How to get help
|
||||
---------------
|
||||
|
||||
Before you ask for help, please make sure you do the following:
|
||||
|
||||
1. Read the documentation_ thoroughly. If in a hurry, at least use the search
|
||||
field that is provided at top-right on the documentation_ pages. Make sure
|
||||
you read the docs for the Pelican version you are using.
|
||||
2. Use a search engine (e.g., DuckDuckGo, Google) to search for a solution to
|
||||
your problem. Someone may have already found a solution, perhaps in the
|
||||
form of a plugin_ or a specific combination of settings.
|
||||
|
||||
3. Try reproducing the issue in a clean environment, ensuring you are using:
|
||||
|
||||
* latest Pelican release (or an up-to-date git clone of Pelican master)
|
||||
* latest releases of libraries used by Pelican
|
||||
* 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
|
||||
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)::
|
||||
|
||||
cd ~/projects/your-site
|
||||
git clone https://github.com/getpelican/pelican ~/projects/pelican
|
||||
pelican content -s ~/projects/pelican/samples/pelican.conf.py -t ~/projects/pelican/pelican/themes/notmyidea
|
||||
|
||||
If despite the above efforts you still cannot resolve your problem, be sure to
|
||||
include in your inquiry the following information, preferably in the form of
|
||||
links to content uploaded to a `paste service`_, GitHub repository, or other
|
||||
publicly-accessible location:
|
||||
|
||||
* Describe what version of Pelican you are running (output of ``pelican --version``
|
||||
or the HEAD commit hash if you cloned the repo) and how exactly you installed
|
||||
it (the full command you used, e.g. ``pip install pelican``).
|
||||
* If you are looking for a way to get some end result, prepare a detailed
|
||||
description of what the end result should look like (preferably in the form of
|
||||
an image or a mock-up page) and explain in detail what you have done so far to
|
||||
achieve it.
|
||||
* If you are trying to solve some issue, prepare a detailed description of how
|
||||
to reproduce the problem. If the issue cannot be easily reproduced, it cannot
|
||||
be debugged by developers or volunteers. Describe only the **minimum steps**
|
||||
necessary to reproduce it (no extra plugins, etc.).
|
||||
* Upload your settings file or any other custom code that would enable people to
|
||||
reproduce the problem or to see what you have already tried to achieve the
|
||||
desired end result.
|
||||
* Upload detailed and **complete** output logs and backtraces (remember to add
|
||||
the ``--debug`` flag: ``pelican --debug content [...]``)
|
||||
|
||||
.. _documentation: http://docs.getpelican.com/
|
||||
.. _`paste service`: https://dpaste.de/
|
||||
|
||||
Once the above preparation is ready, you can contact people willing to help via
|
||||
(preferably) the ``#pelican`` IRC channel or send a message to ``authors at getpelican dot com``.
|
||||
Remember to include all the information you prepared.
|
||||
|
||||
The #pelican IRC channel
|
||||
........................
|
||||
|
||||
* Because of differing time zones, you may not get an immediate response to your
|
||||
question, but please be patient and stay logged into IRC — someone will almost
|
||||
always respond if you wait long enough (it may take a few hours).
|
||||
* If you don't have an IRC client handy, use the webchat_ for quick feedback.
|
||||
* You can direct your IRC client to the channel using this `IRC link`_ or you
|
||||
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/
|
||||
|
||||
|
||||
Contributing code
|
||||
-----------------
|
||||
|
||||
Before you submit a contribution, please ask whether it is desired so that you
|
||||
don't spend a lot of time working on something that would be rejected for a
|
||||
known reason. Consider also whether your new feature might be better suited as
|
||||
a plugin_ — you can `ask for help`_ to make that determination.
|
||||
|
||||
Using Git and GitHub
|
||||
....................
|
||||
|
||||
* `Create a new git branch`_ specific to your change (as opposed to making
|
||||
your commits in the master branch).
|
||||
* **Don't put multiple unrelated fixes/features in the same branch / pull request.**
|
||||
For example, if you're hacking on a new feature and find a bugfix that
|
||||
doesn't *require* your new feature, **make a new distinct branch and pull
|
||||
request** for the bugfix.
|
||||
* Check for unnecessary whitespace via ``git diff --check`` before committing.
|
||||
* First line of your commit message should start with present-tense verb, be 50
|
||||
characters or less, and include the relevant issue number(s) if applicable.
|
||||
*Example:* ``Ensure proper PLUGIN_PATH behavior. Refs #428.`` If the commit
|
||||
*completely fixes* an existing bug report, please use ``Fixes #585`` or ``Fix
|
||||
#585`` syntax (so the relevant issue is automatically closed upon PR merge).
|
||||
* After the first line of the commit message, add a blank line and then a more
|
||||
detailed explanation (when relevant).
|
||||
* `Squash your commits`_ to eliminate merge commits and ensure a clean and
|
||||
readable commit history.
|
||||
* If you have previously filed a GitHub issue and want to contribute code that
|
||||
addresses that issue, **please use** ``hub pull-request`` instead of using
|
||||
GitHub's web UI to submit the pull request. This isn't an absolute
|
||||
requirement, but makes the maintainers' lives much easier! Specifically:
|
||||
`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.
|
||||
|
||||
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
|
||||
<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.
|
||||
* Make sure your code is compatible with Python 2.7, 3.3, and 3.4 — see our
|
||||
`compatibility cheatsheet`_ for more details.
|
||||
* Add docs and tests for your changes. Undocumented and untested features will
|
||||
not be accepted.
|
||||
* `Run all the tests`_ **on all versions of Python supported by Pelican** to
|
||||
ensure nothing was accidentally broken.
|
||||
|
||||
Check out our `Git Tips`_ page or `ask for help`_ if you
|
||||
need assistance or have any questions about these guidelines.
|
||||
|
||||
.. _`plugin`: http://docs.getpelican.com/en/latest/plugins.html
|
||||
.. _`#pelican IRC channel`: http://webchat.freenode.net/?channels=pelican&uio=d4
|
||||
.. _`Create a new git branch`: https://github.com/getpelican/pelican/wiki/Git-Tips#making-your-changes
|
||||
.. _`Squash your commits`: https://github.com/getpelican/pelican/wiki/Git-Tips#squashing-commits
|
||||
.. _`Run all the tests`: http://docs.getpelican.com/en/latest/contribute.html#running-the-test-suite
|
||||
.. _`Git Tips`: https://github.com/getpelican/pelican/wiki/Git-Tips
|
||||
.. _`PEP8 coding standards`: http://www.python.org/dev/peps/pep-0008/
|
||||
.. _`ask for help`: `How to get help`_
|
||||
.. _`compatibility cheatsheet`: http://docs.getpelican.com/en/latest/contribute.html#python-3-development-tips
|
||||
|
|
@ -1,6 +1,3 @@
|
|||
include *.rst
|
||||
global-include *.py
|
||||
recursive-include pelican *.html *.css *png *.in
|
||||
include LICENSE THANKS
|
||||
recursive-include tests *
|
||||
recursive-exclude tests *.pyc
|
||||
recursive-include pelican *.html *.css *png *.in *.rst *.md *.mkd *.xml *.py
|
||||
include LICENSE THANKS docs/changelog.rst
|
||||
|
|
|
|||
39
README.rst
39
README.rst
|
|
@ -1,9 +1,5 @@
|
|||
Pelican
|
||||
=======
|
||||
|
||||
.. image:: https://secure.travis-ci.org/getpelican/pelican.png?branch=master
|
||||
:target: http://travis-ci.org/#!/getpelican/pelican
|
||||
:alt: Travis-ci: continuous integration status.
|
||||
Pelican |build-status| |coverage-status|
|
||||
========================================
|
||||
|
||||
Pelican is a static site generator, written in Python_.
|
||||
|
||||
|
|
@ -27,9 +23,9 @@ Pelican currently supports:
|
|||
* Publication of articles in multiple languages
|
||||
* Atom/RSS feeds
|
||||
* Code syntax highlighting
|
||||
* Compilation of `LESS CSS`_ (optional)
|
||||
* Import from WordPress, Dotclear, or RSS feeds
|
||||
* Integration with external tools: Twitter, Google Analytics, etc. (optional)
|
||||
* Fast rebuild times thanks to content caching and selective output writing.
|
||||
|
||||
Have a look at the `Pelican documentation`_ for more information.
|
||||
|
||||
|
|
@ -45,16 +41,23 @@ You can access the source code at: https://github.com/getpelican/pelican
|
|||
|
||||
If you feel hackish, have a look at the explanation of `Pelican's internals`_.
|
||||
|
||||
Feedback / Contact us
|
||||
---------------------
|
||||
How to get help, contribute, or provide feedback
|
||||
------------------------------------------------
|
||||
|
||||
If you want to see new features in Pelican, don't hesitate to offer
|
||||
suggestions, clone the repository, etc. There are many ways to contribute_.
|
||||
That's open source, dude!
|
||||
See our `contribution submission and feedback guidelines <CONTRIBUTING.rst>`_.
|
||||
|
||||
Contact me at "alexis at notmyidea dot org" for any request/feedback! You can
|
||||
also join the team at `#pelican on irc.freenode.org
|
||||
<irc://irc.freenode.net/pelican>`_
|
||||
(or if you don't have any IRC client, use `the webchat
|
||||
<http://webchat.freenode.net/?channels=pelican&uio=d4>`_)
|
||||
for quick feedback.
|
||||
.. Links
|
||||
|
||||
.. _Python: http://www.python.org/
|
||||
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
|
||||
.. _Markdown: http://daringfireball.net/projects/markdown/
|
||||
.. _Jinja2: http://jinja.pocoo.org/
|
||||
.. _`Pelican documentation`: http://docs.getpelican.com/
|
||||
.. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html
|
||||
|
||||
.. |build-status| image:: https://travis-ci.org/getpelican/pelican.svg?branch=master
|
||||
:target: https://travis-ci.org/getpelican/pelican
|
||||
:alt: Travis CI: continuous integration status
|
||||
.. |coverage-status| image:: https://img.shields.io/coveralls/getpelican/pelican.svg
|
||||
:target: https://coveralls.io/r/getpelican/pelican
|
||||
:alt: Coveralls: code coverage status
|
||||
|
|
|
|||
175
THANKS
175
THANKS
|
|
@ -1,19 +1,158 @@
|
|||
Some people have helped to improve pelican by contributing features, reporting
|
||||
bugs or giving ideas. Thanks to them !
|
||||
Pelican is a project originally created by Alexis Métaireau
|
||||
<http://notmyidea.org/>, but there are a large number of people that have
|
||||
contributed or implemented key features over time. We do our best to keep this
|
||||
list up-to-date, but you can also have a look at the nice contributor graphs
|
||||
produced by GitHub: https://github.com/getpelican/pelican/graphs/contributors
|
||||
|
||||
- Dan Jacka
|
||||
- solsTiCe on linuxfr for reporting bugs
|
||||
- Guillaume B (Gui13)
|
||||
- Ronny Pfannschmidt
|
||||
- Jérome Renard
|
||||
- Nicolas Martin
|
||||
- David Kulak
|
||||
- Arnaud Bos
|
||||
- nblock (Florian)
|
||||
- Bruno Bord
|
||||
- Laureline Guérin
|
||||
- Samuel Martin
|
||||
- Marcus Fredriksson
|
||||
- Günter Kolousek
|
||||
- Simon Liedtke
|
||||
- Manuel F. Viera
|
||||
If you want to contribute, check the documentation section about how to do so:
|
||||
<http://docs.getpelican.com/en/latest/contribute.html>
|
||||
|
||||
Aaron Kavlie
|
||||
Abhishek L
|
||||
Albrecht Mühlenschulte
|
||||
Aldiantoro Nugroho
|
||||
Alen Mujezinovic
|
||||
Alessandro Martin
|
||||
Alexander Artemenko
|
||||
Alexandre RODIERE
|
||||
Alexis Daboville
|
||||
Alexis Métaireau
|
||||
Allan Whatmough
|
||||
Andrea Crotti
|
||||
Andrew Laski
|
||||
Andrew Spiers
|
||||
Arnaud BOS
|
||||
asselinpaul
|
||||
Axel Haustant
|
||||
Benoît HERVIER
|
||||
Borgar
|
||||
Brandon W Maister
|
||||
Brendan Wholihan
|
||||
Brian C. Lane
|
||||
Brian Hsu
|
||||
Brian St. Pierre
|
||||
Bruno Binet
|
||||
BunnyMan
|
||||
Chenguang Wang
|
||||
Chris Elston
|
||||
Chris McDonald (Wraithan)
|
||||
Chris Streeter
|
||||
Christophe Chauvet
|
||||
Clint Howarth
|
||||
Colin Dunklau
|
||||
Dafydd Crosby
|
||||
Dana Woodman
|
||||
Dave King
|
||||
Dave Mankoff
|
||||
David Beitey
|
||||
David Marble
|
||||
Deniz Turgut (Avaris)
|
||||
derdon
|
||||
Dirkjan Ochtman
|
||||
Dirk Makowski
|
||||
draftcode
|
||||
Edward Delaporte
|
||||
Emily Strickland
|
||||
epatters
|
||||
Eric Case
|
||||
Erik Hetzner
|
||||
FELD Boris
|
||||
Feth Arezki
|
||||
Florian Jacob
|
||||
Florian Preinstorfer
|
||||
Félix Delval
|
||||
Freeculture
|
||||
George V. Reilly
|
||||
Guillaume
|
||||
Guillaume B
|
||||
Guillermo López
|
||||
guillermooo
|
||||
Ian Cordasco
|
||||
Igor Kalnitsky
|
||||
Irfan Ahmad
|
||||
Iuri de Silvio
|
||||
Ivan Dyedov
|
||||
James King
|
||||
James Rowe
|
||||
jawher
|
||||
Jered Boxman
|
||||
Jerome
|
||||
Jiachen Yang
|
||||
Jochen Breuer
|
||||
joe di castro
|
||||
John Kristensen
|
||||
John Mastro
|
||||
Jökull Sólberg Auðunsson
|
||||
Jomel Imperio
|
||||
Joseph Reagle
|
||||
Joshua Adelman
|
||||
Julian Berman
|
||||
Justin Mayer
|
||||
Kevin Deldycke
|
||||
Kyle Fuller
|
||||
Laureline Guerin
|
||||
Leonard Huang
|
||||
Leroy Jiang
|
||||
Marcel Hellkamp
|
||||
Marco Milanesi
|
||||
Marcus Fredriksson
|
||||
Mario Rodas
|
||||
Mark Caudill
|
||||
Martin Brochhaus
|
||||
Massimo Santini
|
||||
Matt Bowcock
|
||||
Matt Layman
|
||||
Meir Kriheli
|
||||
Michael Guntsche
|
||||
Michael Reneer
|
||||
Michael Yanovich
|
||||
Mike Yumatov
|
||||
Mikhail Korobov
|
||||
m-r-r
|
||||
mviera
|
||||
Nico Di Rocco
|
||||
Nicolas Duhamel
|
||||
Nicolas Perriault
|
||||
Nicolas Steinmetz
|
||||
Paul Asselin
|
||||
Pavel Puchkin
|
||||
Perry Roper
|
||||
Peter Desmet
|
||||
Philippe Pepiot
|
||||
Rachid Belaid
|
||||
Randall Degges
|
||||
Ranjhith Kalisamy
|
||||
Remi Rampin
|
||||
Rémy HUBSCHER
|
||||
renhbo
|
||||
Richard Duivenvoorde
|
||||
Rogdham
|
||||
Roman Skvazh
|
||||
Ronny Pfannschmidt
|
||||
Rory McCann
|
||||
Rıdvan Örsvuran
|
||||
saghul
|
||||
sam
|
||||
Samrat Man Singh
|
||||
Simon Conseil
|
||||
Simon Liedtke
|
||||
Skami18
|
||||
solsTiCe d'Hiver
|
||||
Steve Schwarz
|
||||
Stéphane Bunel
|
||||
Stéphane Raimbault
|
||||
Stuart Colville
|
||||
Talha Mansoor
|
||||
Tarek Ziade
|
||||
Thanos Lefteris
|
||||
Thomas Thurman
|
||||
Tobias
|
||||
Tomi Pieviläinen
|
||||
Trae Blain
|
||||
Tshepang Lekhonkhobe
|
||||
Valentin-Costel Hăloiu
|
||||
Vlad Niculae
|
||||
William Light
|
||||
Wladislaw Merezhko
|
||||
W. Trevor King
|
||||
Zoresvit
|
||||
|
|
|
|||
9
TODO
9
TODO
|
|
@ -1,9 +0,0 @@
|
|||
* Add a way to support pictures (see how sphinx makes that)
|
||||
* Make the program support UTF8-encoded files as input (and later: any encoding?)
|
||||
* Add status support (draft, published, hidden)
|
||||
* Add a serve + automatic generation behaviour.
|
||||
* Recompile only the changed files, not all.
|
||||
* Add a way to make the coffee (or not)
|
||||
* Add a sitemap generator.
|
||||
* read templates from the templates folder per default
|
||||
* add support of github via ghg import
|
||||
30
bumpr.rc
Normal file
30
bumpr.rc
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[bumpr]
|
||||
file = pelican/__init__.py
|
||||
vcs = git
|
||||
clean =
|
||||
python setup.py clean
|
||||
rm -rf *egg-info build dist
|
||||
tests = python -m unittest discover
|
||||
publish = python setup.py sdist bdist_wheel register upload
|
||||
files = README.rst
|
||||
|
||||
[bump]
|
||||
unsuffix = true
|
||||
message = Bump version {version}
|
||||
|
||||
[prepare]
|
||||
part = patch
|
||||
suffix = dev
|
||||
message = Prepare version {version} for next development cycle
|
||||
|
||||
[changelog]
|
||||
file = docs/changelog.rst
|
||||
separator = =
|
||||
bump = {version} ({date:%Y-%m-%d})
|
||||
prepare = Next release
|
||||
|
||||
[readthedoc]
|
||||
url = http://docs.getpelican.com/{tag}
|
||||
|
||||
[commands]
|
||||
bump = sed -i "" "s/last_stable\s*=.*/last_stable = '{version}'/" docs/conf.py
|
||||
|
|
@ -1,8 +1,15 @@
|
|||
# Tests
|
||||
unittest2
|
||||
mock
|
||||
|
||||
# Optional Packages
|
||||
Markdown
|
||||
BeautifulSoup
|
||||
BeautifulSoup4
|
||||
lxml
|
||||
typogrify
|
||||
webassets
|
||||
|
||||
# To perform release
|
||||
bumpr==0.2.0
|
||||
wheel
|
||||
|
||||
# For docs theme
|
||||
sphinx_rtd_theme
|
||||
|
|
|
|||
BIN
docs/_static/pelican.gif
vendored
BIN
docs/_static/pelican.gif
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
BIN
docs/_static/pelican.png
vendored
BIN
docs/_static/pelican.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 6.3 KiB |
12
docs/_static/theme_overrides.css
vendored
Normal file
12
docs/_static/theme_overrides.css
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
/* override table width restrictions */
|
||||
.wy-table-responsive table td, .wy-table-responsive table th {
|
||||
/* !important prevents the common CSS stylesheets from
|
||||
overriding this as on RTD they are loaded after this stylesheet */
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
.wy-table-responsive {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
3
docs/_themes/.gitignore
vendored
3
docs/_themes/.gitignore
vendored
|
|
@ -1,3 +0,0 @@
|
|||
*.pyc
|
||||
*.pyo
|
||||
.DS_Store
|
||||
22
docs/_themes/pelican/layout.html
vendored
22
docs/_themes/pelican/layout.html
vendored
|
|
@ -1,22 +0,0 @@
|
|||
{% extends "basic/layout.html" %}
|
||||
{% block header %}
|
||||
{{ super() }}
|
||||
{% if pagename == 'index' %}
|
||||
<div class=indexwrapper>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block footer %}
|
||||
{% if pagename == 'index' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{# do not display relbars #}
|
||||
{% block relbar1 %}{% endblock %}
|
||||
{% block relbar2 %}
|
||||
{% if theme_github_fork %}
|
||||
<a href="http://github.com/{{ theme_github_fork }}"><img style="position: fixed; top: 0; right: 0; border: 0;"
|
||||
src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block sidebar1 %}{% endblock %}
|
||||
{% block sidebar2 %}{% endblock %}
|
||||
254
docs/_themes/pelican/static/pelican.css_t
vendored
254
docs/_themes/pelican/static/pelican.css_t
vendored
|
|
@ -1,254 +0,0 @@
|
|||
/*
|
||||
* pelican.css_t
|
||||
* ~~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- pelican theme, based on the nature theme
|
||||
*
|
||||
* :copyright: Copyright 2011 by Alexis Metaireau.
|
||||
*/
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 100%;
|
||||
background-color: white;
|
||||
color: #555;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
width: 70%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 0 0 230px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #B1B4B6;
|
||||
}
|
||||
|
||||
div.document {
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #ffffff;
|
||||
color: #3E4349;
|
||||
padding: 0 30px 30px 30px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
color: #555;
|
||||
width: 100%;
|
||||
padding: 13px 0;
|
||||
text-align: center;
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #444;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.related {
|
||||
background-color: #6BA81E;
|
||||
line-height: 32px;
|
||||
color: #fff;
|
||||
text-shadow: 0px 1px 0 #444;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
div.related a {
|
||||
color: #E2F3CC;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
font-size: 0.75em;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper{
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3,
|
||||
div.sphinxsidebar h4 {
|
||||
font-family: Arial, sans-serif;
|
||||
color: #222;
|
||||
font-size: 1.2em;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
padding: 5px 10px;
|
||||
background-color: #ddd;
|
||||
text-shadow: 1px 1px 0 white
|
||||
}
|
||||
|
||||
div.sphinxsidebar h4{
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
|
||||
div.sphinxsidebar p {
|
||||
color: #888;
|
||||
padding: 5px 20px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.topless {
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
margin: 10px 20px;
|
||||
padding: 0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #ccc;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input[type=text]{
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
a {
|
||||
color: #005B81;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #E32E00;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.body h1,
|
||||
div.body h2,
|
||||
div.body h3,
|
||||
div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: Arial, sans-serif;
|
||||
font-weight: normal;
|
||||
color: #212224;
|
||||
margin: 30px 0px 10px 0px;
|
||||
padding: 5px 0 5px 10px;
|
||||
text-shadow: 0px 1px 0 white
|
||||
}
|
||||
|
||||
{% if theme_index_logo %}
|
||||
div.indexwrapper h1 {
|
||||
text-indent: -999999px;
|
||||
background: url({{ theme_index_logo }}) no-repeat center center;
|
||||
height: {{ theme_index_logo_height }};
|
||||
}
|
||||
{% endif %}
|
||||
div.body h1 {
|
||||
border-top: 20px solid white;
|
||||
margin-top: 0;
|
||||
font-size: 250%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.body h2 { font-size: 150%; background-color: #C8D5E3; }
|
||||
div.body h3 { font-size: 120%; background-color: #D8DEE3; }
|
||||
div.body h4 { font-size: 110%; background-color: #D8DEE3; }
|
||||
div.body h5 { font-size: 100%; background-color: #D8DEE3; }
|
||||
div.body h6 { font-size: 100%; background-color: #D8DEE3; }
|
||||
|
||||
a.headerlink {
|
||||
color: #c60f0f;
|
||||
font-size: 0.8em;
|
||||
padding: 0 4px 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
background-color: #c60f0f;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li {
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title + p {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
div.highlight{
|
||||
background-color: #111;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
div.warning {
|
||||
background-color: #ffe4e4;
|
||||
border: 1px solid #f66;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 10px;
|
||||
background-color: #111;
|
||||
color: #fff;
|
||||
line-height: 1.2em;
|
||||
border: 1px solid #C6C9CB;
|
||||
font-size: 1.1em;
|
||||
margin: 1.5em 0 1.5em 0;
|
||||
-webkit-box-shadow: 1px 1px 1px #d8d8d8;
|
||||
-moz-box-shadow: 1px 1px 1px #d8d8d8;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #ecf0f3;
|
||||
color: #222;
|
||||
/* padding: 1px 2px; */
|
||||
font-size: 1.1em;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.viewcode-back {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
div.viewcode-block:target {
|
||||
background-color: #f4debf;
|
||||
border-top: 1px solid #ac9;
|
||||
border-bottom: 1px solid #ac9;
|
||||
}
|
||||
10
docs/_themes/pelican/theme.conf
vendored
10
docs/_themes/pelican/theme.conf
vendored
|
|
@ -1,10 +0,0 @@
|
|||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = pelican.css
|
||||
nosidebar = true
|
||||
pygments_style = fruity
|
||||
|
||||
[options]
|
||||
index_logo_height = 120px
|
||||
index_logo =
|
||||
github_fork =
|
||||
|
|
@ -1,13 +1,143 @@
|
|||
Release history
|
||||
###############
|
||||
|
||||
3.1 (XXXX-XX-XX)
|
||||
3.5.0 (2014-11-04)
|
||||
==================
|
||||
|
||||
* Introduce ``ARTICLE_ORDER_BY`` and ``PAGE_ORDER_BY`` settings to control the
|
||||
order of articles and pages.
|
||||
* Include time zone information in dates rendered in templates.
|
||||
* Expose the reader name in the metadata for articles and pages.
|
||||
* Add the ability to store static files along with content in the same
|
||||
directory as articles and pages using ``{attach}`` in the path.
|
||||
* Prevent Pelican from raising an exception when there are duplicate pieces of
|
||||
metadata in a Markdown file.
|
||||
* Introduce the ``TYPOGRIFY_IGNORE_TAGS`` setting to add HTML tags to be ignored
|
||||
by Typogrify.
|
||||
* Add the ability to use ``-`` in date formats to strip leading zeros. For
|
||||
example, ``%-d/%-m/%y`` will now result in the date ``9/8/12``.
|
||||
* Ensure feed generation is correctly disabled during quickstart configuration.
|
||||
* Fix ``PAGE_EXCLUDES`` and ``ARTICLE_EXCLUDES`` from incorrectly matching
|
||||
sub-directories.
|
||||
* Introduce ``STATIC_EXCLUDE`` setting to add static file excludes.
|
||||
* Fix an issue when using ``PAGINATION_PATTERNS`` while ``RELATIVE_URLS``
|
||||
is enabled.
|
||||
* Fix feed generation causing links to use the wrong language for month
|
||||
names when using other locales.
|
||||
* Fix an issue where the authors list in the simple template wasn't correctly
|
||||
formatted.
|
||||
* Fix an issue when parsing non-string URLs from settings.
|
||||
* Improve consistency of debug and warning messages.
|
||||
|
||||
3.4.0 (2014-07-01)
|
||||
==================
|
||||
|
||||
* Speed up content generation via new caching mechanism
|
||||
* Add selective post generation (instead of always building entire site)
|
||||
* Many documentation improvements, including switching to prettier RtD theme
|
||||
* Add support for multiple content and plugin paths
|
||||
* Add ``:modified:`` metadata field to complement ``:date:``.
|
||||
Used to specify the last date and time an article was updated independently
|
||||
from the date and time it was published.
|
||||
* Add support for multiple authors via new ``:authors:`` metadata field
|
||||
* Watch for changes in static directories when in auto-regeneration mode
|
||||
* Add filters to limit log output when desired
|
||||
* Add language support to drafts
|
||||
* Add ``SLUGIFY_SOURCE`` setting to control how post slugs are generated
|
||||
* Fix many issues relating to locale and encoding
|
||||
* Apply Typogrify filter to post summary
|
||||
* Preserve file metadata (e.g. time stamps) when copying static files to output
|
||||
* Move AsciiDoc support from Pelican core into separate plugin
|
||||
* Produce inline links instead of reference-style links when importing content
|
||||
* Improve handling of ``IGNORE_FILES`` setting behavior
|
||||
* Properly escape symbol characters in tag names (e.g., ``C++``)
|
||||
* Minor tweaks for Python 3.4 compatibility
|
||||
* Add several new signals
|
||||
|
||||
3.3.0 (2013-09-24)
|
||||
==================
|
||||
|
||||
* Drop Python 3.2 support in favor of Python 3.3
|
||||
* Add ``Fabfile`` so Fabric can be used for workflow automation instead of Make
|
||||
* ``OUTPUT_RETENTION`` setting can be used to preserve metadata (e.g., VCS
|
||||
data such as ``.hg`` and ``.git``) from being removed from output directory
|
||||
* Tumblr import
|
||||
* Improve logic and consistency when cleaning output folder
|
||||
* Improve documentation versioning and release automation
|
||||
* Improve pagination flexibility
|
||||
* Rename signals for better consistency (some plugins may need to be updated)
|
||||
* Move metadata extraction from generators to readers; metadata extraction no
|
||||
longer article-specific
|
||||
* Deprecate ``FILES_TO_COPY`` in favor of ``STATIC_PATHS`` and
|
||||
``EXTRA_PATH_METADATA``
|
||||
* Summaries in Markdown posts no longer include footnotes
|
||||
* Remove unnecessary whitespace in output via ``lstrip_blocks`` Jinja parameter
|
||||
* Move PDF generation from core to plugin
|
||||
* Replace ``MARKUP`` setting with ``READERS``
|
||||
* Add warning if img tag is missing ``alt`` attribute
|
||||
* Add support for ``{}`` in relative links syntax, besides ``||``
|
||||
* Add support for ``{tag}`` and ``{category}`` relative links
|
||||
* Add a ``content_written`` signal
|
||||
|
||||
3.2.1 and 3.2.2
|
||||
===============
|
||||
|
||||
* Facilitate inclusion in FreeBSD Ports Collection
|
||||
|
||||
3.2 (2013-04-24)
|
||||
================
|
||||
|
||||
* Support for Python 3!
|
||||
* Override page save-to location from meta-data (enables using a static page as
|
||||
the site's home page, for example)
|
||||
* Time period archives (per-year, per-month, and per-day archives of posts)
|
||||
* Posterous blog import
|
||||
* Improve WordPress blog import
|
||||
* Migrate plugins to separate repository
|
||||
* Improve HTML parser
|
||||
* Provide ability to show or hide categories from menu using
|
||||
``DISPLAY_CATEGORIES_ON_MENU`` option
|
||||
* Auto-regeneration can be told to ignore files via ``IGNORE_FILES`` setting
|
||||
* Improve post-generation feedback to user
|
||||
* For multilingual posts, use meta-data to designate which is the original
|
||||
and which is the translation
|
||||
* Add ``.mdown`` to list of supported Markdown file extensions
|
||||
* Document-relative URL generation (``RELATIVE_URLS``) is now off by default
|
||||
|
||||
3.1 (2012-12-04)
|
||||
================
|
||||
|
||||
* Importer now stores slugs within files by default. This can be disabled with
|
||||
the ``--disable-slugs`` option.
|
||||
* Improve handling of links to intra-site resources
|
||||
* Ensure WordPress import adds paragraphs for all types of line endings
|
||||
in post content
|
||||
* Decode HTML entities within WordPress post titles on import
|
||||
* Improve appearance of LinkedIn icon in default theme
|
||||
* Add GitHub and Google+ social icons support in default theme
|
||||
* Optimize social icons
|
||||
* Add ``FEED_ALL_ATOM`` and ``FEED_ALL_RSS`` to generate feeds containing all posts regardless of their language
|
||||
* Split ``TRANSLATION_FEED`` into ``TRANSLATION_FEED_ATOM`` and ``TRANSLATION_FEED_RSS``
|
||||
* Different feeds can now be enabled/disabled individually
|
||||
* Allow for blank author: if ``AUTHOR`` setting is not set, author won't
|
||||
default to ``${USER}`` anymore, and a post won't contain any author
|
||||
information if the post author is empty
|
||||
* Move LESS and Webassets support from Pelican core to plugin
|
||||
* The ``DEFAULT_DATE`` setting now defaults to ``None``, which means that
|
||||
articles won't be generated unless date metadata is specified
|
||||
* Add ``FILENAME_METADATA`` setting to support metadata extraction from filename
|
||||
* Add ``gzip_cache`` plugin to compress common text files into a ``.gz``
|
||||
file within the same directory as the original file, preventing the server
|
||||
(e.g. Nginx) from having to compress files during an HTTP call
|
||||
* Add support for AsciiDoc-formatted content
|
||||
* Add ``USE_FOLDER_AS_CATEGORY`` setting so that feature can be toggled on/off
|
||||
* Support arbitrary Jinja template files
|
||||
* Restore basic functional tests
|
||||
* New signals: ``generator_init``, ``get_generators``, and
|
||||
``article_generate_preread``
|
||||
|
||||
3.0 (2012-08-08)
|
||||
==================
|
||||
================
|
||||
|
||||
* Refactored the way URLs are handled
|
||||
* Improved the English documentation
|
||||
|
|
|
|||
77
docs/conf.py
77
docs/conf.py
|
|
@ -1,49 +1,80 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
import sys, os
|
||||
|
||||
sys.path.append(os.path.abspath('..'))
|
||||
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
|
||||
|
||||
from pelican import __version__, __major__
|
||||
sys.path.append(os.path.abspath(os.pardir))
|
||||
|
||||
from pelican import __version__
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
templates_path = ['_templates']
|
||||
extensions = ['sphinx.ext.autodoc',]
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.ifconfig', 'sphinx.ext.extlinks']
|
||||
source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
project = u'Pelican'
|
||||
copyright = u'2010, Alexis Metaireau and contributors'
|
||||
project = 'Pelican'
|
||||
copyright = '2014, Alexis Metaireau and contributors'
|
||||
exclude_patterns = ['_build']
|
||||
version = __version__
|
||||
release = __major__
|
||||
release = __version__
|
||||
version = '.'.join(release.split('.')[:1])
|
||||
last_stable = '3.3.0'
|
||||
rst_prolog = '''
|
||||
.. |last_stable| replace:: :pelican-doc:`{0}`
|
||||
'''.format(last_stable)
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
extlinks = {
|
||||
'pelican-doc': ('http://docs.getpelican.com/%s/', '')
|
||||
}
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
html_theme_path = ['_themes']
|
||||
html_theme = 'pelican'
|
||||
|
||||
html_theme_options = {
|
||||
'nosidebar': True,
|
||||
'index_logo': 'pelican.png',
|
||||
'github_fork': 'getpelican/pelican',
|
||||
}
|
||||
html_theme = 'default'
|
||||
if not on_rtd:
|
||||
try:
|
||||
import sphinx_rtd_theme
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Pelicandoc'
|
||||
|
||||
html_use_smartypants = True
|
||||
|
||||
# If false, no module index is generated.
|
||||
html_use_modindex = False
|
||||
|
||||
# If false, no index is generated.
|
||||
html_use_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
html_show_sourcelink = False
|
||||
|
||||
|
||||
def setup(app):
|
||||
# overrides for wide tables in RTD theme
|
||||
app.add_stylesheet('theme_overrides.css') # path relative to _static
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
latex_documents = [
|
||||
('index', 'Pelican.tex', u'Pelican Documentation',
|
||||
u'Alexis Métaireau', 'manual'),
|
||||
('index', 'Pelican.tex', 'Pelican Documentation',
|
||||
'Alexis Métaireau', 'manual'),
|
||||
]
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
man_pages = [
|
||||
('index', 'pelican', u'pelican documentation',
|
||||
[u'Alexis Métaireau'], 1),
|
||||
('pelican-themes', 'pelican-themes', u'A theme manager for Pelican',
|
||||
[u'Mickaël Raybaud'], 1),
|
||||
('themes', 'pelican-theming', u'How to create themes for Pelican',
|
||||
[u'The Pelican contributors'], 1)
|
||||
('index', 'pelican', 'pelican documentation',
|
||||
['Alexis Métaireau'], 1),
|
||||
('pelican-themes', 'pelican-themes', 'A theme manager for Pelican',
|
||||
['Mickaël Raybaud'], 1),
|
||||
('themes', 'pelican-theming', 'How to create themes for Pelican',
|
||||
['The Pelican contributors'], 1)
|
||||
]
|
||||
|
|
|
|||
505
docs/content.rst
Normal file
505
docs/content.rst
Normal file
|
|
@ -0,0 +1,505 @@
|
|||
Writing content
|
||||
###############
|
||||
|
||||
Articles and pages
|
||||
==================
|
||||
|
||||
Pelican considers "articles" to be chronological content, such as posts on a
|
||||
blog, and thus associated with a date.
|
||||
|
||||
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).
|
||||
|
||||
.. _internal_metadata:
|
||||
|
||||
File metadata
|
||||
=============
|
||||
|
||||
Pelican tries to be smart enough to get the information it needs from the
|
||||
file system (for instance, about the category of your articles), but some
|
||||
information you need to provide in the form of metadata inside your files.
|
||||
|
||||
If you are writing your content in reStructuredText format, you can provide
|
||||
this metadata in text files via the following syntax (give your file the
|
||||
``.rst`` extension)::
|
||||
|
||||
My super title
|
||||
##############
|
||||
|
||||
:date: 2010-10-03 10:20
|
||||
:modified: 2010-10-04 18:40
|
||||
:tags: thats, awesome
|
||||
:category: yeah
|
||||
:slug: my-super-post
|
||||
:authors: Alexis Metaireau, Conan Doyle
|
||||
:summary: Short version for index and feeds
|
||||
|
||||
Pelican implements an extension to reStructuredText to enable support for the
|
||||
``abbr`` HTML tag. To use it, write something like this in your post::
|
||||
|
||||
This will be turned into :abbr:`HTML (HyperText Markup Language)`.
|
||||
|
||||
You can also use Markdown syntax (with a file ending in ``.md``,
|
||||
``.markdown``, ``.mkd``, or ``.mdown``). Markdown generation requires that you
|
||||
first explicitly install the ``Markdown`` package, which can be done via ``pip
|
||||
install Markdown``.
|
||||
|
||||
Pelican also supports `Markdown Extensions`_, which might have to be installed
|
||||
separately if they are not included in the default ``Markdown`` package and can
|
||||
be configured and loaded via the ``MD_EXTENSIONS`` setting.
|
||||
|
||||
Metadata syntax for Markdown posts should follow this pattern::
|
||||
|
||||
Title: My super title
|
||||
Date: 2010-12-03 10:20
|
||||
Modified: 2010-12-05 19:30
|
||||
Category: Python
|
||||
Tags: pelican, publishing
|
||||
Slug: my-super-post
|
||||
Authors: Alexis Metaireau, Conan Doyle
|
||||
Summary: Short version for index and feeds
|
||||
|
||||
This is the content of my super blog post.
|
||||
|
||||
Readers for additional formats (such as AsciiDoc_) are available via plugins.
|
||||
Refer to `pelican-plugins`_ repository for those.
|
||||
|
||||
Pelican can also process HTML files ending in ``.html`` and ``.htm``. Pelican
|
||||
interprets the HTML in a very straightforward manner, reading metadata from
|
||||
``meta`` tags, the title from the ``title`` tag, and the body out from the
|
||||
``body`` tag::
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>My super title</title>
|
||||
<meta name="tags" content="thats, awesome" />
|
||||
<meta name="date" content="2012-07-09 22:28" />
|
||||
<meta name="modified" content="2012-07-10 20:14" />
|
||||
<meta name="category" content="yeah" />
|
||||
<meta name="authors" content="Alexis Métaireau, Conan Doyle" />
|
||||
<meta name="summary" content="Short version for index and feeds" />
|
||||
</head>
|
||||
<body>
|
||||
This is the content of my super blog post.
|
||||
</body>
|
||||
</html>
|
||||
|
||||
With HTML, there is one simple exception to the standard metadata: ``tags`` can
|
||||
be specified either via the ``tags`` metadata, as is standard in Pelican, or
|
||||
via the ``keywords`` metadata, as is standard in HTML. The two can be used
|
||||
interchangeably.
|
||||
|
||||
Note that, aside from the title, none of this article metadata is mandatory:
|
||||
if the date is not specified and ``DEFAULT_DATE`` is set to ``fs``, Pelican
|
||||
will rely on the file's "mtime" timestamp, and the category can be determined
|
||||
by the directory in which the file resides. For example, a file located at
|
||||
``python/foobar/myfoobar.rst`` will have a category of ``foobar``. If you would
|
||||
like to organize your files in other ways where the name of the subfolder would
|
||||
not be a good category name, you can set the setting ``USE_FOLDER_AS_CATEGORY``
|
||||
to ``False``. When parsing dates given in the page metadata, Pelican supports
|
||||
the W3C's `suggested subset ISO 8601`__.
|
||||
|
||||
.. note::
|
||||
|
||||
When experimenting with different settings (especially the metadata
|
||||
ones) caching may interfere and the changes may not be visible. In
|
||||
such cases disable caching with ``LOAD_CONTENT_CACHE = False`` or
|
||||
use the ``--ignore-cache`` command-line switch.
|
||||
|
||||
__ `W3C ISO 8601`_
|
||||
|
||||
``modified`` should be last time you updated the article, and defaults to ``date`` if not specified.
|
||||
Besides you can show ``modified`` in the templates, feed entries in feed readers will be updated automatically
|
||||
when you set ``modified`` to the current date after you modified your article.
|
||||
|
||||
``authors`` is a comma-separated list of article authors. If there's only one author you
|
||||
can use ``author`` field.
|
||||
|
||||
If you do not explicitly specify summary metadata for a given post, the
|
||||
``SUMMARY_MAX_LENGTH`` setting can be used to specify how many words from the
|
||||
beginning of an article are used as the summary.
|
||||
|
||||
You can also extract any metadata from the filename through a regular
|
||||
expression to be set in the ``FILENAME_METADATA`` setting. All named groups
|
||||
that are matched will be set in the metadata object. The default value for the
|
||||
``FILENAME_METADATA`` setting will only extract the date from the filename. For
|
||||
example, if you would like to extract both the date and the slug, you could set
|
||||
something like: ``'(?P<date>\d{4}-\d{2}-\d{2})_(?P<slug>.*)'``
|
||||
|
||||
Please note that the metadata available inside your files takes precedence over
|
||||
the metadata extracted from the filename.
|
||||
|
||||
Pages
|
||||
=====
|
||||
|
||||
If you create a folder named ``pages`` inside the content folder, all the
|
||||
files in it will be used to generate static pages, such as **About** or
|
||||
**Contact** pages. (See example filesystem layout below.)
|
||||
|
||||
You can use the ``DISPLAY_PAGES_ON_MENU`` setting to control whether all those
|
||||
pages are displayed in the primary navigation menu. (Default is ``True``.)
|
||||
|
||||
If you want to exclude any pages from being linked to or listed in the menu
|
||||
then add a ``status: hidden`` attribute to its metadata. This is useful for
|
||||
things like making error pages that fit the generated theme of your site.
|
||||
|
||||
.. _ref-linking-to-internal-content:
|
||||
|
||||
Linking to internal content
|
||||
===========================
|
||||
|
||||
From Pelican 3.1 onwards, it is now possible to specify intra-site links to
|
||||
files in the *source content* hierarchy instead of files in the *generated*
|
||||
hierarchy. This makes it easier to link from the current post to other content
|
||||
that may be sitting alongside that post (instead of having to determine where
|
||||
the other content will be placed after site generation).
|
||||
|
||||
To link to internal content (files in the ``content`` directory), use the
|
||||
following syntax for the link target: ``{filename}path/to/file``
|
||||
|
||||
For example, a Pelican project might be structured like this::
|
||||
|
||||
website/
|
||||
├── content
|
||||
│ ├── category/
|
||||
│ │ └── article1.rst
|
||||
│ ├── article2.md
|
||||
│ └── pages
|
||||
│ └── about.md
|
||||
└── pelican.conf.py
|
||||
|
||||
In this example, ``article1.rst`` could look like this::
|
||||
|
||||
The first article
|
||||
#################
|
||||
|
||||
:date: 2012-12-01 10:02
|
||||
|
||||
See below intra-site link examples in reStructuredText format.
|
||||
|
||||
`a link relative to the current file <{filename}../article2.md>`_
|
||||
`a link relative to the content root <{filename}/article2.md>`_
|
||||
|
||||
and ``article2.md``::
|
||||
|
||||
Title: The second article
|
||||
Date: 2012-12-01 10:02
|
||||
|
||||
See below intra-site link examples in Markdown format.
|
||||
|
||||
[a link relative to the current file]({filename}category/article1.rst)
|
||||
[a link relative to the content root]({filename}/category/article1.rst)
|
||||
|
||||
Linking to static files
|
||||
-----------------------
|
||||
|
||||
Linking to non-article or non-page content uses the same ``{filename}`` syntax
|
||||
as described above. It is important to remember that those files will not be
|
||||
copied to the output directory unless the source directories containing them
|
||||
are included in the ``STATIC_PATHS`` setting of the project's ``pelicanconf.py``
|
||||
file. Pelican's default configuration includes the ``images`` directory for
|
||||
this, but others must be added manually. Forgetting to do so will result in
|
||||
broken links.
|
||||
|
||||
For example, a project's content directory might be structured like this::
|
||||
|
||||
content
|
||||
├── images
|
||||
│ └── han.jpg
|
||||
├── pdfs
|
||||
│ └── menu.pdf
|
||||
└── pages
|
||||
└── test.md
|
||||
|
||||
``test.md`` would include::
|
||||
|
||||

|
||||
[Our Menu]({filename}/pdfs/menu.pdf)
|
||||
|
||||
``pelicanconf.py`` would include::
|
||||
|
||||
STATIC_PATHS = ['images', 'pdfs']
|
||||
|
||||
Site generation would then copy ``han.jpg`` to ``output/images/han.jpg``,
|
||||
``menu.pdf`` to ``output/pdfs/menu.pdf``, and write the appropriate links
|
||||
in ``test.md``.
|
||||
|
||||
Mixed content in the same directory
|
||||
-----------------------------------
|
||||
|
||||
Starting with Pelican 3.5, static files can safely share a source directory with
|
||||
page source files, without exposing the page sources in the generated site.
|
||||
Any such directory must be added to both ``STATIC_PATHS`` and ``PAGE_PATHS``
|
||||
(or ``STATIC_PATHS`` and ``ARTICLE_PATHS``). Pelican will identify and process
|
||||
the page source files normally, and copy the remaining files as if they lived
|
||||
in a separate directory reserved for static files.
|
||||
|
||||
Note: Placing static and content source files together in the same source
|
||||
directory does not guarantee that they will end up in the same place in the
|
||||
generated site. The easiest way to do this is by using the ``{attach}`` link
|
||||
syntax (described below). Alternatively, the ``STATIC_SAVE_AS``,
|
||||
``PAGE_SAVE_AS``, and ``ARTICLE_SAVE_AS`` settings (and the corresponding
|
||||
``*_URL`` settings) can be configured to place files of different types
|
||||
together, just as they could in earlier versions of Pelican.
|
||||
|
||||
Attaching static files
|
||||
----------------------
|
||||
|
||||
Starting with Pelican 3.5, static files can be "attached" to a page or article
|
||||
using this syntax for the link target: ``{attach}path/to/file`` This works
|
||||
like the ``{filename}`` syntax, but also relocates the static file into the
|
||||
linking document's output directory. If the static file originates from a
|
||||
subdirectory beneath the linking document's source, that relationship will be
|
||||
preserved on output. Otherwise, it will become a sibling of the linking
|
||||
document.
|
||||
|
||||
This only works for linking to static files, and only when they originate from
|
||||
a directory included in the ``STATIC_PATHS`` setting.
|
||||
|
||||
For example, a project's content directory might be structured like this::
|
||||
|
||||
content
|
||||
├── blog
|
||||
│ ├── icons
|
||||
│ │ └── icon.png
|
||||
│ ├── photo.jpg
|
||||
│ └── testpost.md
|
||||
└── downloads
|
||||
└── archive.zip
|
||||
|
||||
``pelicanconf.py`` would include::
|
||||
|
||||
PATH = 'content'
|
||||
STATIC_PATHS = ['blog', 'downloads']
|
||||
ARTICLE_PATHS = ['blog']
|
||||
ARTICLE_SAVE_AS = '{date:%Y}/{slug}.html'
|
||||
ARTICLE_URL = '{date:%Y}/{slug}.html'
|
||||
|
||||
``testpost.md`` would include::
|
||||
|
||||
Title: Test Post
|
||||
Category: test
|
||||
Date: 2014-10-31
|
||||
|
||||

|
||||

|
||||
[Downloadable File]({attach}/downloads/archive.zip)
|
||||
|
||||
Site generation would then produce an output directory structured like this::
|
||||
|
||||
output
|
||||
└── 2014
|
||||
├── archive.zip
|
||||
├── icons
|
||||
│ └── icon.png
|
||||
├── photo.jpg
|
||||
└── test-post.html
|
||||
|
||||
Notice that all the files linked using ``{attach}`` ended up in or beneath
|
||||
the article's output directory.
|
||||
|
||||
If a static file is linked multiple times, the relocating feature of
|
||||
``{attach}`` will only work in the first of those links to be processed.
|
||||
After the first link, Pelican will treat ``{attach}`` like ``{filename}``.
|
||||
This avoids breaking the already-processed links.
|
||||
|
||||
**Be careful when linking to a file from multiple documents:**
|
||||
Since the first link to a file finalizes its location and Pelican does
|
||||
not define the order in which documents are processed, using ``{attach}`` on a
|
||||
file linked by multiple documents can cause its location to change from one
|
||||
site build to the next. (Whether this happens in practice will depend on the
|
||||
operating system, file system, version of Pelican, and documents being added,
|
||||
modified, or removed from the project.) Any external sites linking to the
|
||||
file's old location might then find their links broken. **It is therefore
|
||||
advisable to use {attach} only if you use it in all links to a file, and only
|
||||
if the linking documents share a single directory.** Under these conditions,
|
||||
the file's output location will not change in future builds. In cases where
|
||||
these precautions are not possible, consider using ``{filename}`` links instead
|
||||
of ``{attach}``, and letting the file's location be determined by the project's
|
||||
``STATIC_SAVE_AS`` and ``STATIC_URL`` settings. (Per-file ``save_as`` and
|
||||
``url`` overrides can still be set in ``EXTRA_PATH_METADATA``.)
|
||||
|
||||
Linking to tags and categories
|
||||
------------------------------
|
||||
|
||||
You can link to tags and categories using the ``{tag}tagname`` and
|
||||
``{category}foobar`` syntax.
|
||||
|
||||
Deprecated internal link syntax
|
||||
-------------------------------
|
||||
|
||||
To remain compatible with earlier versions, Pelican still supports vertical bars
|
||||
(``||``) in addition to curly braces (``{}``) for internal links. For example:
|
||||
``|filename|an_article.rst``, ``|tag|tagname``, ``|category|foobar``.
|
||||
The syntax was changed from ``||`` to ``{}`` to avoid collision with Markdown
|
||||
extensions or reST directives. Support for the old syntax may eventually be
|
||||
removed.
|
||||
|
||||
|
||||
Importing an existing site
|
||||
==========================
|
||||
|
||||
It is possible to import your site from WordPress, Tumblr, Dotclear, and RSS
|
||||
feeds using a simple script. See :ref:`import`.
|
||||
|
||||
Translations
|
||||
============
|
||||
|
||||
It is possible to translate articles. To do so, you need to add a ``lang`` meta
|
||||
attribute to your articles/pages and set a ``DEFAULT_LANG`` setting (which is
|
||||
English [en] by default). With those settings in place, only articles with the
|
||||
default language will be listed, and each article will be accompanied by a list
|
||||
of available translations for that article.
|
||||
|
||||
.. note::
|
||||
|
||||
This core Pelican functionality does not create sub-sites
|
||||
(e.g. ``example.com/de``) with translated templates for each
|
||||
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
|
||||
metadata; if not set explicitly, Pelican will auto-generate the slug from the
|
||||
title of the article.
|
||||
|
||||
Here is an example of two articles, one in English and the other in French.
|
||||
|
||||
The English article::
|
||||
|
||||
Foobar is not dead
|
||||
##################
|
||||
|
||||
:slug: foobar-is-not-dead
|
||||
:lang: en
|
||||
|
||||
That's true, foobar is still alive!
|
||||
|
||||
And the French version::
|
||||
|
||||
Foobar n'est pas mort !
|
||||
#######################
|
||||
|
||||
:slug: foobar-is-not-dead
|
||||
:lang: fr
|
||||
|
||||
Oui oui, foobar est toujours vivant !
|
||||
|
||||
Post content quality notwithstanding, you can see that only item in common
|
||||
between the two articles is the slug, which is functioning here as an
|
||||
identifier. If you'd rather not explicitly define the slug this way, you must
|
||||
then instead ensure that the translated article titles are identical, since the
|
||||
slug will be auto-generated from the article title.
|
||||
|
||||
If you do not want the original version of one specific article to be detected
|
||||
by the ``DEFAULT_LANG`` setting, use the ``translation`` metadata to specify
|
||||
which posts are translations::
|
||||
|
||||
Foobar is not dead
|
||||
##################
|
||||
|
||||
:slug: foobar-is-not-dead
|
||||
:lang: en
|
||||
:translation: true
|
||||
|
||||
That's true, foobar is still alive!
|
||||
|
||||
|
||||
.. _internal_pygments_options:
|
||||
|
||||
Syntax highlighting
|
||||
===================
|
||||
|
||||
Pelican is able to provide colorized syntax highlighting for your code blocks.
|
||||
To do so, you have to use the following conventions inside your content files.
|
||||
|
||||
For reStructuredText, use the code-block directive::
|
||||
|
||||
.. code-block:: identifier
|
||||
|
||||
<indented code block goes here>
|
||||
|
||||
For Markdown, include the language identifier just above the code block,
|
||||
indenting both the identifier and code::
|
||||
|
||||
A block of text.
|
||||
|
||||
:::identifier
|
||||
<code goes here>
|
||||
|
||||
The specified identifier (e.g. ``python``, ``ruby``) should be one that
|
||||
appears on the `list of available lexers <http://pygments.org/docs/lexers/>`_.
|
||||
|
||||
When using reStructuredText the following options are available in the
|
||||
code-block directive:
|
||||
|
||||
============= ============ =========================================
|
||||
Option Valid values Description
|
||||
============= ============ =========================================
|
||||
anchorlinenos N/A If present wrap line numbers in <a> tags.
|
||||
classprefix string String to prepend to token class names
|
||||
hl_lines numbers List of lines to be highlighted.
|
||||
lineanchors string Wrap each line in an anchor using this
|
||||
string and -linenumber.
|
||||
linenos string If present or set to "table" output line
|
||||
numbers in a table, if set to
|
||||
"inline" output them inline. "none" means
|
||||
do not output the line numbers for this
|
||||
table.
|
||||
linenospecial number If set every nth line will be given the
|
||||
'special' css class.
|
||||
linenostart number Line number for the first line.
|
||||
linenostep number Print every nth line number.
|
||||
lineseparator string String to print between lines of code,
|
||||
'\n' by default.
|
||||
linespans string Wrap each line in a span using this and
|
||||
-linenumber.
|
||||
nobackground N/A If set do not output background color for
|
||||
the wrapping element
|
||||
nowrap N/A If set do not wrap the tokens at all.
|
||||
tagsfile string ctags file to use for name definitions.
|
||||
tagurlformat string format for the ctag links.
|
||||
============= ============ =========================================
|
||||
|
||||
Note that, depending on the version, your Pygments module might not have
|
||||
all of these options available. Refer to the *HtmlFormatter* section of the
|
||||
`Pygments documentation <http://pygments.org/docs/formatters/>`_ for more
|
||||
details on each of the options.
|
||||
|
||||
For example, the following code block enables line numbers, starting at 153,
|
||||
and prefixes the Pygments CSS classes with *pgcss* to make the names
|
||||
more unique and avoid possible CSS conflicts::
|
||||
|
||||
.. code-block:: identifier
|
||||
:classprefix: pgcss
|
||||
:linenos: table
|
||||
:linenostart: 153
|
||||
|
||||
<indented code block goes here>
|
||||
|
||||
It is also possible to specify the ``PYGMENTS_RST_OPTIONS`` variable in your
|
||||
Pelican settings file to include options that will be automatically applied to
|
||||
every code block.
|
||||
|
||||
For example, if you want to have line numbers displayed for every code block
|
||||
and a CSS prefix you would set this variable to::
|
||||
|
||||
PYGMENTS_RST_OPTIONS = {'classprefix': 'pgcss', 'linenos': 'table'}
|
||||
|
||||
If specified, settings for individual code blocks will override the defaults in
|
||||
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
|
||||
listed on the index page nor on any category or tag page.
|
||||
|
||||
.. _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/
|
||||
.. _i18n_subsites plugin: http://github.com/getpelican/pelican-plugins/tree/master/i18n_subsites
|
||||
|
|
@ -1,29 +1,34 @@
|
|||
How to contribute?
|
||||
###################
|
||||
There are many ways to contribute to Pelican. You can enhance the
|
||||
documentation, add missing features, and fix bugs (or just report them).
|
||||
Contributing and feedback guidelines
|
||||
####################################
|
||||
|
||||
Don't hesitate to fork and make a pull request on GitHub. When doing so, please
|
||||
create a new feature branch as opposed to making your commits in the master
|
||||
branch.
|
||||
There are many ways to contribute to Pelican. You can improve the
|
||||
documentation, add missing features, and fix bugs (or just report them). You
|
||||
can also help out by reviewing and commenting on
|
||||
`existing issues <https://github.com/getpelican/pelican/issues>`_.
|
||||
|
||||
Don't hesitate to fork Pelican and submit an issue or pull request on GitHub.
|
||||
When doing so, please adhere to the following guidelines.
|
||||
|
||||
.. include:: ../CONTRIBUTING.rst
|
||||
|
||||
Setting up the development environment
|
||||
======================================
|
||||
|
||||
You're free to set up your development environment any way you like. Here is a
|
||||
way using the `virtualenv <http://www.virtualenv.org/>`_ and `virtualenvwrapper
|
||||
<http://www.doughellmann.com/projects/virtualenvwrapper/>`_ tools. If you don't
|
||||
have them, you can install these both of these packages via::
|
||||
While there are many ways to set up one's development environment, following
|
||||
is a method that uses `virtualenv <http://www.virtualenv.org/>`_. If you don't
|
||||
have ``virtualenv`` installed, you can install it via::
|
||||
|
||||
$ pip install virtualenvwrapper
|
||||
$ 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 a virtual environment, use the following syntax::
|
||||
To create and activate a virtual environment, use the following syntax::
|
||||
|
||||
$ mkvirtualenv pelican
|
||||
$ virtualenv ~/virtualenvs/pelican
|
||||
$ cd ~/virtualenvs/pelican
|
||||
$ . bin/activate
|
||||
|
||||
To clone the Pelican source::
|
||||
|
||||
|
|
@ -38,32 +43,164 @@ To install Pelican and its dependencies::
|
|||
|
||||
$ python setup.py develop
|
||||
|
||||
Or using ``pip``::
|
||||
|
||||
$ pip install -e .
|
||||
|
||||
Building the docs
|
||||
=================
|
||||
|
||||
If you make changes to the documentation, you should preview your changes
|
||||
before committing them::
|
||||
|
||||
$ pip install sphinx
|
||||
$ cd src/pelican/docs
|
||||
$ make html
|
||||
|
||||
Open ``_build/html/index.html`` in your browser to preview the documentation.
|
||||
|
||||
Running the test suite
|
||||
======================
|
||||
|
||||
Each time you add a feature, there are two things to do regarding tests:
|
||||
checking that the existing tests pass, and adding tests for the new feature
|
||||
check that the existing tests pass, and add tests for the new feature
|
||||
or bugfix.
|
||||
|
||||
The tests live in "pelican/tests" and you can run them using the
|
||||
"discover" feature of unittest2::
|
||||
The tests live in ``pelican/tests`` and you can run them using the
|
||||
"discover" feature of ``unittest``::
|
||||
|
||||
$ unit2 discover
|
||||
$ python -m unittest discover
|
||||
|
||||
If you have made changes that affect the output of a Pelican-generated weblog,
|
||||
then you should update the output used by functional tests.
|
||||
To do so, you can use the following two commands::
|
||||
After making your changes and running the tests, you may see a test failure
|
||||
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, you can use the following two commands::
|
||||
|
||||
$ LC_ALL="C" pelican -o tests/output/custom/ -s samples/pelican.conf.py \
|
||||
$ LC_ALL=en_US.utf8 pelican -o pelican/tests/output/custom/ \
|
||||
-s samples/pelican.conf.py samples/content/
|
||||
$ LC_ALL=fr_FR.utf8 pelican -o pelican/tests/output/custom_locale/ \
|
||||
-s samples/pelican.conf_FR.py samples/content/
|
||||
$ LC_ALL=en_US.utf8 pelican -o pelican/tests/output/basic/ \
|
||||
samples/content/
|
||||
$ LC_ALL="C" USER="Dummy Author" pelican -o tests/output/basic/ samples/content/
|
||||
|
||||
Coding standards
|
||||
================
|
||||
Testing on Python 2 and 3
|
||||
-------------------------
|
||||
|
||||
Try to respect what is described in the `PEP8 specification
|
||||
<http://www.python.org/dev/peps/pep-0008/>`_ when providing patches. This can be
|
||||
eased via the `pep8 <http://pypi.python.org/pypi/pep8>`_ or `flake8
|
||||
<http://pypi.python.org/pypi/flake8/>`_ tools, the latter of which in
|
||||
particular will give you some useful hints about ways in which the
|
||||
code/formatting can be improved.
|
||||
Testing on Python 3 currently requires some extra steps: installing
|
||||
Python 3-compatible versions of dependent packages and plugins.
|
||||
|
||||
Tox_ is a useful tool to run tests on both versions. It will install the
|
||||
Python 3-compatible version of dependent packages.
|
||||
|
||||
.. _Tox: http://testrun.org/tox/latest/
|
||||
|
||||
Python 3 development tips
|
||||
=========================
|
||||
|
||||
Here are some tips that may be useful when doing some code for both Python 2.7
|
||||
and Python 3 at the same time:
|
||||
|
||||
- Assume every string and literal is unicode (import unicode_literals):
|
||||
|
||||
- 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 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.:
|
||||
|
||||
- ``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,
|
||||
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.
|
||||
|
||||
|
||||
Logging tips
|
||||
============
|
||||
|
||||
Try to use logging with appropriate levels.
|
||||
|
||||
For logging messages that are not repeated, use the usual Python way::
|
||||
|
||||
# at top of file
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# when needed
|
||||
logger.warning("A warning with %s formatting", arg_to_be_formatted)
|
||||
|
||||
Do not format log messages yourself. Use ``%s`` formatting in messages and pass
|
||||
arguments to logger. This is important, because Pelican logger will preprocess
|
||||
some arguments (like Exceptions) for Py2/Py3 compatibility.
|
||||
|
||||
Limiting extraneous log messages
|
||||
--------------------------------
|
||||
|
||||
If the log message can occur several times, you may want to limit the log to
|
||||
prevent flooding. In order to do that, use the ``extra`` keyword argument for
|
||||
the logging message in the following format::
|
||||
|
||||
logger.warning("A warning with %s formatting", arg_to_be_formatted,
|
||||
extra={'limit_msg': 'A generic message for too many warnings'})
|
||||
|
||||
Optionally, you can also set ``'limit_args'`` as a tuple of arguments in
|
||||
``extra`` dict if your generic message needs formatting.
|
||||
|
||||
Limit is set to ``5``, i.e, first four logs with the same ``'limit_msg'`` are
|
||||
outputted normally but the fifth one will be logged using
|
||||
``'limit_msg'`` (and ``'limit_args'`` if present). After the fifth,
|
||||
corresponding log messages will be ignored.
|
||||
|
||||
For example, if you want to log missing resources, use the following code::
|
||||
|
||||
for resource in resources:
|
||||
if resource.is_missing:
|
||||
logger.warning(
|
||||
'The resource %s is missing', resource.name,
|
||||
extra={'limit_msg': 'Other resources were missing'})
|
||||
|
||||
The log messages will be displayed as follows::
|
||||
|
||||
WARNING: The resource prettiest_cat.jpg is missing
|
||||
WARNING: The resource best_cat_ever.jpg is missing
|
||||
WARNING: The resource cutest_cat.jpg is missing
|
||||
WARNING: The resource lolcat.jpg is missing
|
||||
WARNING: Other resources were missing
|
||||
|
||||
|
||||
Outputting traceback in the logs
|
||||
--------------------------------
|
||||
|
||||
If you're logging inside an ``except`` block, you may want to provide the
|
||||
traceback information as well. You can do that by setting ``exc_info`` keyword
|
||||
argument to ``True`` during logging. However, doing so by default can be
|
||||
undesired because tracebacks are long and can be confusing to regular users.
|
||||
Try to limit them to ``--debug`` mode like the following::
|
||||
|
||||
try:
|
||||
some_action()
|
||||
except Exception as e:
|
||||
logger.error('Exception occurred: %s', e,
|
||||
exc_info=settings.get('DEBUG', False))
|
||||
|
|
|
|||
217
docs/faq.rst
217
docs/faq.rst
|
|
@ -1,52 +1,63 @@
|
|||
Frequently Asked Questions (FAQ)
|
||||
################################
|
||||
|
||||
Here is a summary of the frequently asked questions for Pelican.
|
||||
Here are some frequently asked questions about Pelican.
|
||||
|
||||
What's the best way to communicate a problem, question, or suggestion?
|
||||
======================================================================
|
||||
|
||||
If you have a problem, question, or suggestion, please start by striking up a
|
||||
conversation on `#pelican on Freenode <irc://irc.freenode.net/pelican>`_.
|
||||
Those who don't have an IRC client handy can jump in immediately via
|
||||
`IRC webchat <http://webchat.freenode.net/?channels=pelican&uio=d4>`_. Because
|
||||
of differing time zones, you may not get an immediate response to your question,
|
||||
but please be patient and stay logged into IRC — someone will almost always
|
||||
respond.
|
||||
|
||||
If you are unable to resolve your issue or if you have a feature request, please
|
||||
refer to the `issue tracker <https://github.com/getpelican/pelican/issues>`_.
|
||||
Please read our :doc:`feedback guidelines <contribute>`.
|
||||
|
||||
How can I help?
|
||||
================
|
||||
|
||||
There are several ways to help out. First, you can use Pelican and report any
|
||||
suggestions or problems you might have via IRC or the issue tracker.
|
||||
There are several ways to help out. First, you can report any Pelican
|
||||
suggestions or problems you might have via IRC or the `issue tracker
|
||||
<https://github.com/getpelican/pelican/issues>`_. If submitting an issue
|
||||
report, please first check the existing issue list (both open and closed) in
|
||||
order to avoid submitting a duplicate issue.
|
||||
|
||||
If you want to contribute, please fork `the git repository
|
||||
<https://github.com/getpelican/pelican/>`_, create a new feature branch, make
|
||||
your changes, and issue a pull request. Someone will review your changes as soon
|
||||
as possible. Please refer to the :doc:`How to Contribute <contribute>` section
|
||||
for more details.
|
||||
your changes, and issue a pull request. Someone will review your changes as
|
||||
soon as possible. Please refer to the :doc:`How to Contribute <contribute>`
|
||||
section for more details.
|
||||
|
||||
You can also contribute by creating themes and improving the documentation.
|
||||
|
||||
Is it mandatory to have a configuration file?
|
||||
=============================================
|
||||
|
||||
No, it's not. Configuration files are just an easy way to configure Pelican.
|
||||
Configuration files are optional and are just an easy way to configure Pelican.
|
||||
For basic operations, it's possible to specify options while invoking Pelican
|
||||
via the command line. See ``pelican --help`` for more information.
|
||||
|
||||
Changes to the setting file take no effect
|
||||
==========================================
|
||||
|
||||
When experimenting with different settings (especially the metadata
|
||||
ones) caching may interfere and the changes may not be visible. In
|
||||
such cases disable caching with ``LOAD_CONTENT_CACHE = False`` or
|
||||
use the ``--ignore-cache`` command-line switch.
|
||||
|
||||
I'm creating my own theme. How do I use Pygments for syntax highlighting?
|
||||
=========================================================================
|
||||
|
||||
Pygments adds some classes to the generated content. These classes are used by
|
||||
themes to style code syntax highlighting via CSS. Specifically, you can
|
||||
customize the appearance of your syntax highlighting via the ``.codehilite pre``
|
||||
customize the appearance of your syntax highlighting via the ``.highlight pre``
|
||||
class in your theme's CSS file. To see how various styles can be used to render
|
||||
Django code, for example, you can use the demo `on the project website
|
||||
<http://pygments.org/demo/15101/>`_.
|
||||
Django code, for example, use the style selector drop-down at top-right on the
|
||||
`Pygments project demo site <http://pygments.org/demo/>`_.
|
||||
|
||||
You can use the following example commands to generate a starting CSS file from
|
||||
a Pygments built-in style (in this case, "monokai") and then copy the generated
|
||||
CSS file to your new theme::
|
||||
|
||||
pygmentize -S monokai -f html -a .highlight > pygment.css
|
||||
cp pygment.css path/to/theme/static/css/
|
||||
|
||||
Don't forget to import your ``pygment.css`` file from your main CSS file.
|
||||
|
||||
How do I create my own theme?
|
||||
==============================
|
||||
|
|
@ -54,18 +65,18 @@ How do I create my own theme?
|
|||
Please refer to :ref:`theming-pelican`.
|
||||
|
||||
I want to use Markdown, but I got an error.
|
||||
===========================================
|
||||
==========================================================================
|
||||
|
||||
Markdown is not a hard dependency for Pelican, so you will need to explicitly
|
||||
install it. You can do so by typing::
|
||||
If you try to generate Markdown content without first installing the Markdown
|
||||
library, may see a message that says ``No valid files found in content``.
|
||||
Markdown is not a hard dependency for Pelican, so if you have content in
|
||||
Markdown format, you will need to explicitly install the Markdown library.
|
||||
You can do so by typing the following command, prepending ``sudo`` if
|
||||
permissions require it::
|
||||
|
||||
$ (sudo) pip install markdown
|
||||
pip install markdown
|
||||
|
||||
In case you don't have pip installed, consider installing it via::
|
||||
|
||||
$ (sudo) easy_install pip
|
||||
|
||||
Can I use arbitrary meta-data in my templates?
|
||||
Can I use arbitrary metadata in my templates?
|
||||
==============================================
|
||||
|
||||
Yes. For example, to include a modified date in a Markdown post, one could
|
||||
|
|
@ -73,56 +84,160 @@ include the following at the top of the article::
|
|||
|
||||
Modified: 2012-08-08
|
||||
|
||||
That meta-data can then be accessed in the template::
|
||||
For reStructuredText, this metadata should of course be prefixed with a colon::
|
||||
|
||||
:Modified: 2012-08-08
|
||||
|
||||
This metadata can then be accessed in templates such as ``article.html`` via::
|
||||
|
||||
{% if article.modified %}
|
||||
Last modified: {{ article.modified }}
|
||||
{% endif %}
|
||||
|
||||
If you want to include metadata in templates outside the article context (e.g.,
|
||||
``base.html``), the ``if`` statement should instead be::
|
||||
|
||||
{% if article and article.modified %}
|
||||
|
||||
How do I assign custom templates on a per-page basis?
|
||||
=====================================================
|
||||
|
||||
It's as simple as adding an extra line of metadata to any pages or articles you
|
||||
want to have its own template.
|
||||
It's as simple as adding an extra line of metadata to any page or article that
|
||||
you want to have its own template. For example, this is how it would be handled
|
||||
for content in reST format::
|
||||
|
||||
:template: template_name
|
||||
|
||||
Then just make sure to have the template installed in to your theme as
|
||||
``template_name.html``.
|
||||
For content in Markdown format::
|
||||
|
||||
Template: template_name
|
||||
|
||||
Then just make sure your theme contains the relevant template file (e.g.
|
||||
``template_name.html``).
|
||||
|
||||
How can I override the generated URL of a specific page or article?
|
||||
===================================================================
|
||||
|
||||
Include ``url`` and ``save_as`` metadata in any pages or articles that you want
|
||||
to override the generated URL. Here is an example page in reST format::
|
||||
|
||||
Override url/save_as page
|
||||
#########################
|
||||
|
||||
:url: override/url/
|
||||
:save_as: override/url/index.html
|
||||
|
||||
With this metadata, the page will be written to ``override/url/index.html``
|
||||
and Pelican will use url ``override/url/`` to link to this page.
|
||||
|
||||
How can I use a static page as my home page?
|
||||
============================================
|
||||
|
||||
The override feature mentioned above can be used to specify a static page as
|
||||
your home page. The following Markdown example could be stored in
|
||||
``content/pages/home.md``::
|
||||
|
||||
Title: Welcome to My Site
|
||||
URL:
|
||||
save_as: index.html
|
||||
|
||||
Thank you for visiting. Welcome!
|
||||
|
||||
If the original blog index is still wanted, it can then be saved in a
|
||||
different location by setting ``INDEX_SAVE_AS = 'blog_index.html`` for
|
||||
the ``''index'`` direct template.
|
||||
|
||||
What if I want to disable feed generation?
|
||||
==========================================
|
||||
|
||||
To disable all feed generation set ``FEED_ATOM`` and ``FEED_RSS`` to ``None`` in
|
||||
your settings. Please note ``None`` and ``''`` are not the same thing. The
|
||||
word ``None`` should not be surrounded by quotes.
|
||||
To disable feed generation, all feed settings should be set to ``None``.
|
||||
All but three feed settings already default to ``None``, so if you want to
|
||||
disable all feed generation, you only need to specify the following settings::
|
||||
|
||||
FEED_ALL_ATOM = None
|
||||
CATEGORY_FEED_ATOM = None
|
||||
TRANSLATION_FEED_ATOM = None
|
||||
AUTHOR_FEED_ATOM = None
|
||||
AUTHOR_FEED_RSS = None
|
||||
|
||||
The word ``None`` should not be surrounded by quotes. Please note that ``None``
|
||||
and ``''`` are not the same thing.
|
||||
|
||||
I'm getting a warning about feeds generated without SITEURL being set properly
|
||||
==============================================================================
|
||||
|
||||
`RSS and Atom feeds require all URLs and links in them to be absolute
|
||||
`RSS and Atom feeds require all URL links to be absolute
|
||||
<http://validator.w3.org/feed/docs/rss2.html#comments>`_.
|
||||
In order to properly generate all URLs properly in Pelican you will need to set
|
||||
``SITEURL`` to the full path of your blog. When using ``make html`` and the
|
||||
default Makefile provided by the `pelican-quickstart` bootstrap script to test
|
||||
build your site, it's normal to see this warning since ``SITEURL`` is
|
||||
deliberately left undefined. If configured properly no other ``make`` commands
|
||||
should result in this warning.
|
||||
In order to properly generate links in Pelican you will need to set ``SITEURL``
|
||||
to the full path of your site.
|
||||
|
||||
Feeds are still generated when this warning is displayed but may not validate.
|
||||
Feeds are still generated when this warning is displayed, but links within may
|
||||
be malformed and thus the feed may not validate.
|
||||
|
||||
My feeds are broken since I upgraded to Pelican 3.0
|
||||
My feeds are broken since I upgraded to Pelican 3.x
|
||||
===================================================
|
||||
|
||||
Starting in 3.0, some of the FEED setting names were changed to more explicitly
|
||||
refer to the Atom feeds they inherently represent (much like the FEED_RSS
|
||||
setting names). Here is an exact list of the renamed setting names::
|
||||
setting names). Here is an exact list of the renamed settings::
|
||||
|
||||
FEED -> FEED_ATOM
|
||||
TAG_FEED -> TAG_FEED_ATOM
|
||||
CATEGORY_FEED -> CATEGORY_FEED_ATOM
|
||||
|
||||
Older 2.x themes that referenced the old setting names may not link properly.
|
||||
In order to rectify this, please update your theme for compatibility with 3.0+
|
||||
by changing the relevant values in your template files. For an example of
|
||||
complete feed headers and usage please check out the ``simple`` theme.
|
||||
Starting in 3.1, the new feed ``FEED_ALL_ATOM`` has been introduced: this
|
||||
feed will aggregate all posts regardless of their language. This setting
|
||||
generates ``'feeds/all.atom.xml'`` by default and ``FEED_ATOM`` now defaults to
|
||||
``None``. The following feed setting has also been renamed::
|
||||
|
||||
TRANSLATION_FEED -> TRANSLATION_FEED_ATOM
|
||||
|
||||
Older themes that referenced the old setting names may not link properly.
|
||||
In order to rectify this, please update your theme for compatibility by changing
|
||||
the relevant values in your template files. For an example of complete feed
|
||||
headers and usage please check out the ``simple`` theme.
|
||||
|
||||
Is Pelican only suitable for blogs?
|
||||
===================================
|
||||
|
||||
No. Pelican can be easily configured to create and maintain any type of static site.
|
||||
This may require a little customization of your theme and Pelican configuration.
|
||||
For example, if you are building a launch site for your product and do not need
|
||||
tags on your site, you could remove the relevant HTML code from your theme.
|
||||
You can also disable generation of tag-related pages via::
|
||||
|
||||
TAGS_SAVE_AS = ''
|
||||
TAG_SAVE_AS = ''
|
||||
|
||||
Why does Pelican always write all HTML files even with content caching enabled?
|
||||
===============================================================================
|
||||
|
||||
In order to reliably determine whether the HTML output is different
|
||||
before writing it, a large part of the generation environment
|
||||
including the template contexts, imported plugins, etc. would have to
|
||||
be saved and compared, at least in the form of a hash (which would
|
||||
require special handling of unhashable types), because of all the
|
||||
possible combinations of plugins, pagination, etc. which may change in
|
||||
many different ways. This would require a lot more processing time
|
||||
and memory and storage space. Simply writing the files each time is a
|
||||
lot faster and a lot more reliable.
|
||||
|
||||
However, this means that the modification time of the files changes
|
||||
every time, so a ``rsync`` based upload will transfer them even if
|
||||
their content hasn't changed. A simple solution is to make ``rsync``
|
||||
use the ``--checksum`` option, which will make it compare the file
|
||||
checksums in a much faster way than Pelican would.
|
||||
|
||||
When only several specific output files are of interest (e.g. when
|
||||
working on some specific page or the theme templates), the
|
||||
`WRITE_SELECTED` option may help, see
|
||||
:ref:`writing_only_selected_content`.
|
||||
|
||||
How to process only a subset of all articles?
|
||||
=============================================
|
||||
|
||||
It is often useful to process only e.g. 10 articles for debugging
|
||||
purposes. This can be achieved by explicitly specifying only the
|
||||
filenames of those articles in ``ARTICLE_PATHS``. A list of such
|
||||
filenames could be found using a command similar to ``cd content;
|
||||
find -name '*.md' | head -n 10``.
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
Trucs et astuces pour Pelican
|
||||
#############################
|
||||
|
||||
Personnaliser l'url d'un article pour Pelican
|
||||
=============================================
|
||||
|
||||
Par défaut, quand vous créez un article ayant pour titre *Mon article pour Pelican*,
|
||||
l'url par défaut devient *mon-article-pour-pelican.html*. Cependant, il est possible
|
||||
de modifier cela en utilisant la technique utilisée pour les traductions d'article,
|
||||
c'est à dire le paramètre *:slug:* ::
|
||||
|
||||
Mon article pour Pelican
|
||||
########################
|
||||
|
||||
:date: 2011-01-31 11:05
|
||||
:slug: super-article-pour-pelican
|
||||
|
||||
bla, bla, bla …
|
||||
|
||||
En prenant cet exemple ci dessus, votre url deviendra *super-article-pour-pelican.html*
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
Les bases de Pelican
|
||||
####################
|
||||
|
||||
Créer son premier article
|
||||
=========================
|
||||
|
||||
Pour créer notre premier article, nous allons éditer un fichier, par exemple premier_article.rst ::
|
||||
|
||||
Premier article pour Pelican
|
||||
############################
|
||||
:author: Guillaume
|
||||
:date: 2011-01-08 10:20
|
||||
:category: GNU-Linux
|
||||
:tags: tutoriel, git
|
||||
Ceci est un tutoriel pour configurer git.
|
||||
Bla, bla, bla ....
|
||||
|
||||
Maintenant que ce fichier est créé, on va lancer la création du blog ::
|
||||
|
||||
pelican .
|
||||
|
||||
Vous aller obtenir une sortie comme celle ci — $PATH représente le dossier où vous
|
||||
avez créé votre article ::
|
||||
|
||||
[ok] writing $PATH/output/feeds/all.atom.xml
|
||||
[ok] writing $PATH/output/feeds/GNU/Linux.atom.xml
|
||||
[ok] writing $PATH/output/feeds/all-en.atom.xml
|
||||
[ok] writing $PATH/output/premier-article-pour-pelican.html
|
||||
[ok] writing $PATH/output/index.html
|
||||
[ok] writing $PATH/output/tags.html
|
||||
[ok] writing $PATH/output/categories.html
|
||||
[ok] writing $PATH/output/archives.html
|
||||
[ok] writing $PATH/output/tag/tutoriel.html
|
||||
[ok] writing $PATH/output/tag/git.html
|
||||
[ok] writing $PATH/output/category/GNU-Linux.html
|
||||
|
||||
|
||||
Première analyse
|
||||
================
|
||||
|
||||
Nous allons décortiquer un peu tout ça ensemble.
|
||||
|
||||
* Un dossier output/ a été créé pour y mettre le fichiers xml et html du blog.
|
||||
* Dans le dossier feeds/, nous retrouvons les différents flux de syndication.
|
||||
* Le fichier de l’article et la page principale du blog a été généré.
|
||||
* Le répertoire tag/ propose une page par tag.
|
||||
* La page correspondant à la catégorie est générée dans le répertoire category/
|
||||
|
||||
Si vous ouvrez le fichier index.html — ou un autre — avec votre navigateur, vous
|
||||
remarquerez que :
|
||||
|
||||
* Le thème utilisé par défaut est notmyidea
|
||||
* Le nom du blog est A Pelican Blog.
|
||||
|
||||
Bien évidemment, il y a des paramètres de base que l’on peut modifier pour mettre
|
||||
un peu tout ça à sa sauce. C’est ce que nous allons voir au travers du fichier de configuration.
|
||||
|
||||
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
Fichier de configuration
|
||||
************************
|
||||
|
||||
On va créer un fichier de configuration que l’on va appeler **settings.py**. On peut
|
||||
utiliser Pelican sans faire ce fichier, mais il faudrait à chaque fois passer les paramètres
|
||||
en ligne de commande. Et comme il va nous servir à faire d’autres choses bien utile,
|
||||
autant l’appréhender de suite. Cependant, nous n’allons voir que la base pour l’instant.
|
||||
|
||||
Paramètres de base
|
||||
==================
|
||||
|
||||
AUTHOR :
|
||||
Désigne l’auteur par défaut ;
|
||||
|
||||
DEFAULT_CATEGORY :
|
||||
La catégorie par défaut des articles. Si ce paramètre n’est
|
||||
pas documenté, il prendra la valeur misc — pour miscellaneous (divers en français) ;
|
||||
|
||||
SITENAME :
|
||||
Le nom de votre site ;
|
||||
|
||||
OUTPUT_PATH :
|
||||
Le répertoire de sortie du blog.
|
||||
|
||||
Quand je dis qu’on va faire simple, on fait simple !
|
||||
Passons donc à ce quoi doit ressembler le fichier de configuration ::
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
AUTHOR = "Guillaume"
|
||||
DEFAULT_CATEGORY = "GNU-Linux"
|
||||
SITENAME = "Free Culture"
|
||||
|
||||
|
||||
Si vous avez un serveur comme Apache de configuré pour votre machine, vous
|
||||
pouvez paramétrer le répertoire de sortie vers **/var/www/blog** par exemple ::
|
||||
|
||||
OUTPUT_PATH = "/var/www/blog"
|
||||
|
||||
Une remarque importante. Si vous avez besoin de passer un caractère accentué, il
|
||||
faut le préciser que la chaine est en unicode en faisant par exemple
|
||||
*AUTHOR = u"Guillaume LAMÉ"*
|
||||
|
||||
Pour bien vérifier que les paramètres sont bien pris en compte, nous allons enlever les lignes *:author: Guillaume* et *:category: GNU-Linux* de notre fichier
|
||||
**premier_article.rst** et regénérer le blog.
|
||||
|
||||
Rafraichissez votre page, ce devrait être bon.
|
||||
|
||||
Nous allons maintenant passer en revue les différents paramètres de Pelican. Je les
|
||||
ai regroupé par thème. Cependant, c’est surtout un listing avant de rentrer dans les
|
||||
détails au prochain chapitre.
|
||||
|
||||
Flux de syndication
|
||||
===================
|
||||
|
||||
CATEGORY_FEED_ATOM :
|
||||
Chemin d’écriture des flux Atom liés aux catégories ;
|
||||
|
||||
CATEGORY_FEED_RSS :
|
||||
Idem pour les flux rss (Optionnel);
|
||||
|
||||
FEED_ATOM :
|
||||
Chemin du flux Atom global ;
|
||||
|
||||
FEED_RSS :
|
||||
Chemin du flux Rss global (Optionnel);
|
||||
|
||||
TAG_FEED_ATOM :
|
||||
Chemin des flux Atom pour les tags (Optionnel);
|
||||
|
||||
TAG_FEED_RSS :
|
||||
Chemin des flux Rss pour les tags (Optionnel).
|
||||
|
||||
|
||||
Traductions
|
||||
===========
|
||||
|
||||
DEFAULT_LANG :
|
||||
Le langage par défaut à utiliser. «*en*» par défaut ;
|
||||
|
||||
TRANSLATION_FEED :
|
||||
Chemin du flux pour les traductions.
|
||||
|
||||
|
||||
Thèmes
|
||||
======
|
||||
|
||||
CSS_FILE :
|
||||
Fichier css à utiliser si celui-ci est différent du fichier par défaut (*main.css*) ;
|
||||
|
||||
DISPLAY_PAGES_ON_MENU :
|
||||
Affiche ou non les pages statiques sur le menu du thème ;
|
||||
|
||||
DISQUS_SITENAME :
|
||||
Indiquer le nom du site spécifié sur Disqus ;
|
||||
|
||||
GITHUB_URL :
|
||||
Indiquez votre url Github ;
|
||||
|
||||
GOOGLE_ANALYTICS :
|
||||
'UA-XXXX-YYYY' pour activer Google analytics ;
|
||||
|
||||
GOSQUARED_SITENAME :
|
||||
'XXX-YYYYYY-X' pour activer GoSquared ;
|
||||
|
||||
JINJA_EXTENSIONS :
|
||||
Liste d'extension Jinja2 que vous souhaitez utiliser ;
|
||||
|
||||
LINKS :
|
||||
Une liste de tuples (Titre, url) pour afficher la liste de lien ;
|
||||
|
||||
PDF_PROCESSOR :
|
||||
Génère ou non les articles et pages au format pdf ;
|
||||
|
||||
NEWEST_FIRST_ARCHIVES :
|
||||
Met les articles plus récent en tête de l'archive ;
|
||||
|
||||
SOCIAL :
|
||||
Une liste de tuples (Titre, url) pour afficher la liste de lien dans la section "Social" ;
|
||||
|
||||
STATIC_THEME_PATHS :
|
||||
Répertoire du thème que vous souhaitez importer dans l'arborescence finale ;
|
||||
|
||||
THEME :
|
||||
Thème à utiliser:
|
||||
|
||||
TWITTER_USERNAME :
|
||||
Permet d'afficher un bouton permettant le tweet des articles.
|
||||
|
||||
Pelican est fournit avec :doc:`pelican-themes`, un script permettant de gérer les thèmes
|
||||
|
||||
|
||||
|
||||
Paramètres divers
|
||||
=================
|
||||
|
||||
DEFAULT_DATE:
|
||||
Date par défaut à utiliser si l'information de date n'est pas spécifiée
|
||||
dans les metadonnées de l'article.
|
||||
Si 'fs', Pelican se basera sur le *mtime* du fichier.
|
||||
Si c'est un tuple, il sera passé au constructeur datetime.datetime pour
|
||||
générer l'objet datetime utilisé par défaut.
|
||||
|
||||
KEEP_OUTPUT DIRECTORY :
|
||||
Ne génère que les fichiers modifiés et n'efface pas le repertoire de sortie ;
|
||||
|
||||
MARKUP :
|
||||
Langage de balisage à utiliser ;
|
||||
|
||||
PATH :
|
||||
Répertoire à suivre pour les fichiers inclus ;
|
||||
|
||||
SITEURL :
|
||||
URL de base de votre site ;
|
||||
|
||||
STATIC_PATHS :
|
||||
Les chemins statiques que vous voulez avoir accès sur le chemin de sortie "statique" ;
|
||||
|
||||
MARKDOWN_EXTENSIONS :
|
||||
Liste des extentions Markdown que vous souhaitez utiliser ;
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
Conventions
|
||||
###########
|
||||
|
||||
Environnement de test
|
||||
=====================
|
||||
|
||||
Les exemples sont basées sur une distribution Debian. Pour les autres distributions,
|
||||
il y aura des ajustements à faire, notamment pour l’installation de Pelican. Les
|
||||
noms des paquets peuvent changer.
|
||||
|
||||
Conventions typographiques
|
||||
==========================
|
||||
|
||||
Un petit rappel concernant les codes sources.
|
||||
|
||||
* $ correspond à une ligne à exécuter en tant qu’utilisateur courant du systême ;
|
||||
* # correspond à une ligne à exécuter en tant que root ;
|
||||
* **settings.py** : Les noms des répertoires et fichiers sont en gras.
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
Foire aux questions (FAQ)
|
||||
#########################
|
||||
|
||||
Voici un résumé des questions fréquemment posées pour pelican.
|
||||
|
||||
Est-il obligatoire d'avoir un fichier de configuration ?
|
||||
========================================================
|
||||
|
||||
Non. Les fichiers de configuration sont juste un moyen facile de configurer
|
||||
pelican. Pour les opérations de base, il est possible de spécifier des
|
||||
options
|
||||
en invoquant pelican avec la ligne de commande (voir pelican --help pour
|
||||
plus
|
||||
d'informations à ce sujet)
|
||||
|
||||
Je crée mon propre thème, comment utiliser pygments?
|
||||
====================================================
|
||||
|
||||
Pygment ajoute quelques classes au contenu généré, de sorte qua colorisation
|
||||
de votre thème se fait grâce à un fichier css. Vous pouvez jeter un oeil à
|
||||
celui proposé par`sur le site du projet <http://pygments.org/demo/15101/>`_
|
||||
|
||||
Comment puis-je créer mon propre thèm
|
||||
=====================================
|
||||
|
||||
Vueillez vous référer à :ref:`theming-pelican-fr`.
|
||||
|
||||
Comment puis-je aider?
|
||||
======================
|
||||
|
||||
Vous avez plusieurs options pour aider. Tout d'abord, vous pouvez utiliser
|
||||
le
|
||||
pélican, et signaler toute idée ou problème que vous avez sur le bugtracker
|
||||
.
|
||||
|
||||
Si vous voulez contribuer, jeter un oeil au dépôt git , ajoutez vos
|
||||
modifications et faites une demande, je les regarderai dès que possible
|
||||
|
||||
Vous pouvez aussi contribuer en créant des thèmes, et/ou compléter la
|
||||
documentation.
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
Pelican
|
||||
#######
|
||||
|
||||
Pelican est un generateur de blog simple codé en python
|
||||
|
||||
* Écrivez vos articles directement dans votre éditeur favori (vim !) et
|
||||
directement en syntaxe reStructuredText ou Markdown ;
|
||||
* Un outil simple en ligne de conmmande pour (re)générer le blog ;
|
||||
* Sortie complètement statique, facile pour l'héberger n'importe où ;
|
||||
|
||||
Fonctionnalités
|
||||
===============
|
||||
|
||||
Pelican supporte actuellement :
|
||||
|
||||
* des articles de blog ;
|
||||
* des pages statiques ;
|
||||
* les commentaires via un service externe (`disqus <http://disqus.com>`_)
|
||||
Notez qu'étant bien un service externe assez pratique, vous ne gérez pas
|
||||
vous même les commentaires. Ce qui pourrait occasionner une perte de vos données;
|
||||
* support de template (les templates sont crées avec `jinja2 <http://jinjna.pocoo.org>`_) ;
|
||||
* génération optionnelle de vos pages et articles en pdf.
|
||||
|
||||
Pourquoi le nom "Pelican" ?
|
||||
============================
|
||||
|
||||
Vous n'avez pas remarqué ? "Pelican" est un anagramme pour "Calepin" ;)
|
||||
|
||||
Code source
|
||||
===========
|
||||
|
||||
Vous pouvez accéder au code source via git à l'adresse
|
||||
http://github.com/getpelican/pelican/
|
||||
|
||||
Feedback !
|
||||
==========
|
||||
|
||||
Si vous voulez de nouvelles fonctionnalitées pour Pelican, n'hésitez pas à nous le dire,
|
||||
à cloner le dépôt, etc … C'est open source !!!
|
||||
|
||||
Contactez Alexis à "alexis at notmyidea dot org" pour quelques requêtes ou retour d'expérience que ce soi !
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
conventions
|
||||
installation
|
||||
bases
|
||||
configuration
|
||||
themes
|
||||
parametres_article
|
||||
astuces
|
||||
faq
|
||||
pelican-themes
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
Installation et mise à jour de Pelican
|
||||
######################################
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
Il y a deux façons d’installer Pelican sur son système. La première est via l’utilitaire
|
||||
pip, l’autre façon est de télécharger Pelican via Github. Ici nous allons voir les deux
|
||||
façons de procéder.
|
||||
|
||||
Via pip
|
||||
-------
|
||||
|
||||
Pour installer Pelican via pip, vous aurez besoin du paquet python-pip. puis installez Pelican ::
|
||||
|
||||
# apt-get install python-pip
|
||||
# pip install pelican
|
||||
|
||||
|
||||
Via Github
|
||||
----------
|
||||
|
||||
Pour installer Pelican en reprenant le code via Github, nous aurons besoin du paquet
|
||||
git-core pour récupérez les sources de Pelican. Puis nous procédons à l’installation ::
|
||||
|
||||
# apt-get install git-core
|
||||
$ git clone https://github.com/getpelican/pelican.git
|
||||
$ cd pelican
|
||||
# python setup.py install
|
||||
|
||||
Mises à jour
|
||||
============
|
||||
|
||||
Via pip
|
||||
-------
|
||||
|
||||
Rien de bien compliqué pour mettre à jour via pip ::
|
||||
|
||||
$ cd votreRepertoireSource
|
||||
$ pip install --upgrade pelican
|
||||
|
||||
|
||||
Via Github
|
||||
----------
|
||||
|
||||
C'est un peu plus long avec Github par contre ::
|
||||
|
||||
$ cd votreRepertoireSource
|
||||
$ git pull origin master
|
||||
$ cd pelican
|
||||
# python setup.py install
|
||||
|
||||
Vous aurez un message d’erreur si le module setuptools de python n’est pas installé.
|
||||
La manipulation est la suivante ::
|
||||
|
||||
# apt-get install python-setuptools
|
||||
|
||||
Alors, quelle méthode choisir ?
|
||||
===============================
|
||||
|
||||
Vous avez le choix entre deux méthodes, mais aussi entre deux concepts. La méthode
|
||||
de Github est la version de développement, où les modifications arrivent assez
|
||||
fréquemment sans être testées à fond. La version de pip est une version arrêtée avec un
|
||||
numéro de version dans laquelle vous aurez moins de bug. N’oubliez cependant pas
|
||||
que le projet est très jeune et manque donc de maturité. Si vous aimez avoir les toutes
|
||||
dernières versions utilisez Github, sinon penchez vous sur pip.
|
||||
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
Les paramètres des articles dans Pelican
|
||||
########################################
|
||||
|
||||
Les catégories
|
||||
==============
|
||||
|
||||
Nous avons vu que pour affecter un article à une catégorie, nous avions le paramètre *:category:*.
|
||||
Il y a cependant plus simple, affecter un répertoire à une catégorie.
|
||||
|
||||
Dans le répertoire ou vous avez vos articles, créez le repertoire **GNU-Linux** et déplacez y le fichier
|
||||
**premier_article.rst**. Bien évidemment nous ne verront pas la différence, car jusqu'ici *GNU-Linux*
|
||||
est notre catégorie par défaut.
|
||||
|
||||
Nous allons faire un autre exemple d'article avec la catégorie Pelican. Créez le répertoire **Pelican**
|
||||
et collez cette exemple d'article ::
|
||||
|
||||
Préparation de la documentation
|
||||
###############################
|
||||
|
||||
:date: 2011-01-27 15:28
|
||||
:tags: documentation
|
||||
|
||||
Il y a quand même pas mal de boulot pour faire une documentation !
|
||||
|
||||
Et lancez la compilation du blog. Vous voyez que la catégorie est affectée automatiquement.
|
||||
|
||||
Les tags
|
||||
========
|
||||
|
||||
Pour les tags, il n'y a rien de compliqué. il suffit de mettre le(s) tags séparés si besoin d'une virgule. ::
|
||||
|
||||
Préparation de la documentation
|
||||
###############################
|
||||
|
||||
:date: 2011-01-27 15:28
|
||||
:tags: documentation, pelican
|
||||
|
||||
Par contre, par soucis de clarté au niveau des url je vous conseille de mettre les expression de plusieurs
|
||||
mots séparées par des tirets ::
|
||||
|
||||
:tags: mise-a-jour
|
||||
|
||||
et non ::
|
||||
|
||||
:tags: mise a jour
|
||||
|
||||
|
||||
Les auteurs
|
||||
===========
|
||||
|
||||
Par défaut, vous pouvez indiqué votre nom en tant qu'auteur dans le fichier de configuration.
|
||||
S'il y a plusieurs auteurs pour le site, vous pouvez le définir manuellement dans
|
||||
l'article avec la méta-donnée ::
|
||||
|
||||
:author: Guillaume
|
||||
|
||||
La date
|
||||
=======
|
||||
|
||||
La date se met au format anglophone : **YYYY-MM-DD hh:mm** ::
|
||||
|
||||
:date: 2011-01-31 14:12
|
||||
|
||||
|
||||
Les traductions
|
||||
===============
|
||||
|
||||
Pelican permet de générer un blog multilingue assez facilement. Pour cela nous devons :
|
||||
|
||||
* Définir la langue de base du blog ;
|
||||
* Donner une référence à l'article initial ;
|
||||
* Définir la langue du fichier traduit et y reporter la référence.
|
||||
|
||||
Pour définir la langue de base nous allons modifier le fichier **settings.py** et y rajouter la ligne suivante ::
|
||||
|
||||
DEFAULT_LANG = "fr"
|
||||
|
||||
Puis ajouter la référence dans notre article d'origine qui deviendra ::
|
||||
|
||||
Préparation de la documentation
|
||||
###############################
|
||||
|
||||
:date: 2011-01-27 15:28
|
||||
:tags: documentation
|
||||
:slug: preparation-de-la-documentation
|
||||
|
||||
Il y a quand même pas mal de boulot pour faire une documentation !
|
||||
|
||||
Nous n'avons plus qu'à créer l'article en anglais ::
|
||||
|
||||
Start of documentation
|
||||
######################
|
||||
|
||||
:slug: preparation-de-la-documention
|
||||
:lang: en
|
||||
|
||||
There are still a lot of work to documentation !
|
||||
|
||||
**Il est important de comprendre que la valeur de :slug: deviendra votre url. Ne mettez donc pas un diminutif pour
|
||||
identifier l'article**
|
||||
|
||||
Rien de plus à savoir pour traduire efficacement des articles.
|
||||
|
||||
|
||||
Maintenant que vous avez toutes les clés en main pour créer un article, nous allons passer à la personnalisation
|
||||
du fichier de configuration.
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
pelican-themes
|
||||
##############
|
||||
|
||||
|
||||
|
||||
Description
|
||||
===========
|
||||
|
||||
``pelican-themes`` est un outil en lignes de commandes pour gérer les thèmes de Pelican.
|
||||
|
||||
|
||||
Utilisation:
|
||||
""""""""""""
|
||||
|
||||
| pelican-themes [-h] [-l] [-i *chemin d'un thème* [*chemin d'un thème* ...]]
|
||||
| [-r *nom d'un thème* [*nom d'un thème* ...]]
|
||||
| [-s *chemin d'un thème* [*chemin d'un thème* ...]] [-v] [--version]
|
||||
|
||||
Arguments:
|
||||
""""""""""
|
||||
|
||||
|
||||
-h, --help Afficher l'aide et quitter
|
||||
|
||||
-l, --list Montrer les thèmes installés
|
||||
|
||||
-i chemin, --install chemin Chemin(s) d'accès d'un ou plusieurs thème à installer
|
||||
|
||||
-r nom, --remove nom Noms d'un ou plusieurs thèmes à installer
|
||||
|
||||
-s chemin, --symlink chemin Fonctionne de la même façon que l'option ``--install``, mais crée un lien symbolique au lieu d'effectuer une copie du thème vers le répertoire des thèmes.
|
||||
Utile pour le développement de thèmes.
|
||||
|
||||
-v, --verbose Sortie détaillée
|
||||
|
||||
--version Affiche la version du script et quitte
|
||||
|
||||
|
||||
|
||||
Exemples
|
||||
========
|
||||
|
||||
|
||||
Lister les thèmes installés
|
||||
"""""""""""""""""""""""""""
|
||||
|
||||
``pelican-themes`` peut afficher les thèmes disponibles.
|
||||
|
||||
Pour cela, vous pouvez utiliser l'option ``-l`` ou ``--list``, comme ceci:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pelican-themes -l
|
||||
notmyidea
|
||||
two-column@
|
||||
simple
|
||||
$ pelican-themes --list
|
||||
notmyidea
|
||||
two-column@
|
||||
simple
|
||||
|
||||
Dans cet exemple, nous voyons qu'il y a trois thèmes d'installés: ``notmyidea``, ``simple`` and ``two-column``.
|
||||
|
||||
``two-column`` est suivi d'un ``@`` par ce que c'est un lien symbolique (voir `Créer des liens symboliques`_).
|
||||
|
||||
Notez que vous pouvez combiner l'option ``--list`` avec l'option ``--verbose``, pour afficher plus de détails:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pelican-themes -v -l
|
||||
/usr/local/lib/python2.6/dist-packages/pelican-2.6.0-py2.6.egg/pelican/themes/notmyidea
|
||||
/usr/local/lib/python2.6/dist-packages/pelican-2.6.0-py2.6.egg/pelican/themes/two-column (symbolic link to `/home/skami/Dev/Python/pelican-themes/two-column')
|
||||
/usr/local/lib/python2.6/dist-packages/pelican-2.6.0-py2.6.egg/pelican/themes/simple
|
||||
|
||||
|
||||
Installer des thèmes
|
||||
""""""""""""""""""""
|
||||
|
||||
Vous pouvez installer un ou plusieurs thèmes en utilisant l'option ``-i`` ou ``--install``.
|
||||
|
||||
Cette option prends en argument le(s) chemin(s) d'accès du ou des thème(s) que vous voulez installer, et peut se combiner avec l'option ``--verbose``:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# pelican-themes --install ~/Dev/Python/pelican-themes/notmyidea-cms --verbose
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# pelican-themes --install ~/Dev/Python/pelican-themes/notmyidea-cms\
|
||||
~/Dev/Python/pelican-themes/martyalchin \
|
||||
--verbose
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# pelican-themes -vi ~/Dev/Python/pelican-themes/two-column
|
||||
|
||||
|
||||
Supprimer des thèmes
|
||||
""""""""""""""""""""
|
||||
|
||||
``pelican-themes`` peut aussi supprimer des thèmes précédemment installés grâce à l'option ``-r`` ou ``--remove``.
|
||||
|
||||
Cette option prends en argument le ou les nom(s) des thèmes que vous voulez installer, et peux se combiner avec l'option ``--verbose``:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# pelican-themes --remove two-column
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# pelican-themes -r martyachin notmyidea-cmd -v
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Créer des liens symboliques
|
||||
"""""""""""""""""""""""""""
|
||||
|
||||
|
||||
L'option ``-s`` ou ``--symlink`` de ``pelican-themes`` permet de lier symboliquement un thème.
|
||||
|
||||
Cette option s'utilise exactement comme l'option ``--install``:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# pelican-themes --symlink ~/Dev/Python/pelican-themes/two-column
|
||||
|
||||
Dans l'exemple ci dessus, un lien symbolique pointant vers le thème ``two-column`` a été installé dans le répertoire des thèmes de Pelican, toute modification sur le thème ``two-column`` prendra donc effet immédiatement.
|
||||
|
||||
Cela peut être pratique pour le développement de thèmes
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ sudo pelican-themes -s ~/Dev/Python/pelican-themes/two-column
|
||||
$ pelican ~/Blog/content -o /tmp/out -t two-column
|
||||
$ firefox /tmp/out/index.html
|
||||
$ vim ~/Dev/Pelican/pelican-themes/two-coumn/static/css/main.css
|
||||
$ pelican ~/Blog/content -o /tmp/out -t two-column
|
||||
$ cp /tmp/bg.png ~/Dev/Pelican/pelican-themes/two-coumn/static/img/bg.png
|
||||
$ pelican ~/Blog/content -o /tmp/out -t two-column
|
||||
$ vim ~/Dev/Pelican/pelican-themes/two-coumn/templates/index.html
|
||||
$ pelican ~/Blog/content -o /tmp/out -t two-column
|
||||
|
||||
|
||||
Notez que cette fonctionnalité nécessite d'avoir un système d'exploitation et un système de fichiers supportant les liens symboliques, elle n'est donc pas disponible sous Micro$oft®©™ Fenêtre®©™.
|
||||
|
||||
Faire plusieurs choses à la fois
|
||||
""""""""""""""""""""""""""""""""
|
||||
|
||||
|
||||
Les options ``--install``, ``--remove`` et ``--symlink`` peuvent être employées en même temps, ce qui permets de réaliser plusieurs opérations en même temps:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# pelican-themes --remove notmyidea-cms two-column \
|
||||
--install ~/Dev/Python/pelican-themes/notmyidea-cms-fr \
|
||||
--symlink ~/Dev/Python/pelican-themes/two-column \
|
||||
--verbose
|
||||
|
||||
Dans cette exemple, le thème ``notmyidea-cms`` sera remplacé par le thème ``notmyidea-cms-fr`` et le thème ``two-column`` sera lié symboliquement...
|
||||
|
||||
|
||||
|
||||
À voir également
|
||||
================
|
||||
|
||||
- http://docs.notmyidea.org/alexis/pelican/
|
||||
- ``/usr/share/doc/pelican/`` si vous avez installé Pelican par le `dépôt APT <http://skami18.github.com/pelican-packages/>`_
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
.. _theming-pelican:
|
||||
|
||||
Cette page est une traduction de la documentation originale, en anglais et
|
||||
disponible `ici <../themes.html>`_.
|
||||
|
||||
Comment créer des thèmes pour Pelican
|
||||
#####################################
|
||||
|
||||
Pelican utlise le très bon moteur de template `jinja2 <http://jinja.pocoo.org>`_
|
||||
pour produire de l'HTML. La syntaxe de jinja2 est vraiment très simple. Si vous
|
||||
voulez créer votre propre thème, soyez libre de prendre inspiration sur le theme
|
||||
"simple" qui est disponible `ici
|
||||
<https://github.com/getpelican/pelican/tree/master/pelican/themes/simple/templates>`_
|
||||
|
||||
Structure
|
||||
=========
|
||||
|
||||
Pour réaliser votre propre thème vous devez respecter la structure suivante ::
|
||||
|
||||
├── static
|
||||
│ ├── css
|
||||
│ └── images
|
||||
└── templates
|
||||
├── archives.html // pour afficher les archives
|
||||
├── article.html // généré pour chaque article
|
||||
├── categories.html // doit lister toutes les catégories
|
||||
├── category.html // généré pour chaque catégorie
|
||||
├── index.html // la page d'index, affiche tous les articles
|
||||
├── page.html // généré pour chaque page
|
||||
├── tag.html // généré pour chaque tag
|
||||
└── tags.html // doit lister tous les tags. Peut être un nuage de tag.
|
||||
|
||||
|
||||
* `static` contient tout le contenu statique. Il sera copié dans le dossier
|
||||
`theme/static`. J'ai mis un dossier css et un image, mais ce sont juste des
|
||||
exemples. Mettez ce dont vous avez besoin ici.
|
||||
|
||||
* `templates` contient tous les templates qui vont être utiliser pour générer les
|
||||
pages. J'ai juste mis les templates obligatoires ici, vous pouvez définir les
|
||||
vôtres si cela vous aide à vous organiser pendant que vous réaliser le thème.
|
||||
Vous pouvez par exemple utiliser les directives {% include %} et {% extends %}
|
||||
de jinja2.
|
||||
|
||||
Templates et variables
|
||||
======================
|
||||
|
||||
Cela utilise une syntaxe simple, que vous pouvez insérer dans vos pages HTML.
|
||||
Ce document décrit les templates qui doivent exister dans un thème, et quelles
|
||||
variables seront passées à chaque template, au moment de le générer.
|
||||
|
||||
Tous les templates recevront les variables définies dans votre fichier de
|
||||
configuration, si elles sont en capitales. Vous pouvez y accéder directement.
|
||||
|
||||
Variables communes
|
||||
------------------
|
||||
|
||||
Toutes ces variables seront passées à chaque template.
|
||||
|
||||
============= ===================================================
|
||||
Variable Description
|
||||
============= ===================================================
|
||||
articles C'est la liste des articles, ordonnée décroissante
|
||||
par date. Tous les éléments de la liste sont des
|
||||
objets `Article`, vous pouvez donc accéder à leurs
|
||||
propriétés (exemple : title, summary, author, etc).
|
||||
dates La même liste d'articles, ordonnée croissante par
|
||||
date.
|
||||
tags Un dictionnaire contenant tous les tags (clés), et
|
||||
la liste des articles correspondants à chacun
|
||||
d'entre eux (valeur).
|
||||
categories Un dictionnaire contenant toutes les catégories
|
||||
(clés), et la liste des articles correspondants à
|
||||
chacune d'entre elles (valeur).
|
||||
pages La liste des pages.
|
||||
============= ===================================================
|
||||
|
||||
index.html
|
||||
----------
|
||||
|
||||
La page d'accueil de votre blog, sera générée dans output/index.html.
|
||||
|
||||
Si la pagination est activée, les pages suivantes seront à l'adresse
|
||||
output/index`n`.html.
|
||||
|
||||
=================== ===================================================
|
||||
Variable Description
|
||||
=================== ===================================================
|
||||
articles_paginator Un objet paginator de la liste d'articles.
|
||||
articles_page La page actuelle d'articles.
|
||||
dates_paginator Un objet paginator de la liste d'articles, ordonné
|
||||
par date croissante.
|
||||
dates_pages La page actuelle d'articles, ordonnée par date
|
||||
croissante.
|
||||
page_name 'index'.
|
||||
=================== ===================================================
|
||||
|
||||
category.html
|
||||
-------------
|
||||
|
||||
Ce template sera généré pour chaque catégorie existante, et se retrouvera
|
||||
finalement à output/category/`nom de la catégorie`.html.
|
||||
|
||||
Si la pagination est activée, les pages suivantes seront disponibles à
|
||||
l'adresse output/category/`nom de la catégorie``n`.html.
|
||||
|
||||
=================== ===================================================
|
||||
Variable Description
|
||||
=================== ===================================================
|
||||
category La catégorie qui est en train d'être générée.
|
||||
articles Les articles dans cette catégorie.
|
||||
dates Les articles dans cette catégorie, ordonnés par
|
||||
date croissante.
|
||||
articles_paginator Un objet paginator de la liste d'articles.
|
||||
articles_page La page actuelle d'articles.
|
||||
dates_paginator Un objet paginator de la liste d'articles, ordonné
|
||||
par date croissante.
|
||||
dates_pages La page actuelle d'articles, ordonnée par date
|
||||
croissante.
|
||||
page_name 'category/`nom de la catégorie`'.
|
||||
=================== ===================================================
|
||||
|
||||
article.html
|
||||
-------------
|
||||
|
||||
Ce template sera généré pour chaque article. Les fichiers .html seront
|
||||
disponibles à output/`nom de l'article`.html.
|
||||
|
||||
============= ===================================================
|
||||
Variable Description
|
||||
============= ===================================================
|
||||
article L'objet article à afficher.
|
||||
category Le nom de la catégorie de l'article actuel.
|
||||
============= ===================================================
|
||||
|
||||
page.html
|
||||
---------
|
||||
|
||||
Pour chaque page ce template sera généré à l'adresse
|
||||
output/`nom de la page`.html
|
||||
|
||||
============= ===================================================
|
||||
Variable Description
|
||||
============= ===================================================
|
||||
page L'objet page à afficher. Vous pouvez accéder à son
|
||||
titre (title), slug, et son contenu (content).
|
||||
============= ===================================================
|
||||
|
||||
tag.html
|
||||
--------
|
||||
|
||||
Ce template sera généré pour chaque tag. Cela créera des fichiers .html à
|
||||
l'adresse output/tag/`nom du tag`.html.
|
||||
|
||||
Si la pagination est activée, les pages suivantes seront disponibles à
|
||||
l'adresse output/tag/`nom du tag``n`.html
|
||||
|
||||
=================== ===================================================
|
||||
Variable Description
|
||||
=================== ===================================================
|
||||
tag Nom du tag à afficher.
|
||||
articles Une liste des articles contenant ce tag.
|
||||
dates Une liste des articles contenant ce tag, ordonnée
|
||||
par date croissante.
|
||||
articles_paginator Un objet paginator de la liste d'articles.
|
||||
articles_page La page actuelle d'articles.
|
||||
dates_paginator Un objet paginator de la liste d'articles, ordonné
|
||||
par date croissante.
|
||||
dates_pages La page actuelle d'articles, ordonnée par date
|
||||
croissante.
|
||||
page_name 'tag/`nom du tag`'.
|
||||
=================== ===================================================
|
||||
|
|
@ -1,313 +0,0 @@
|
|||
Getting started
|
||||
###############
|
||||
|
||||
Installing Pelican
|
||||
==================
|
||||
|
||||
You're ready? Let's go! You can install Pelican via several different methods.
|
||||
The simplest is via `pip <http://www.pip-installer.org/>`_::
|
||||
|
||||
$ pip install pelican
|
||||
|
||||
If you don't have ``pip`` installed, an alternative method is ``easy_install``::
|
||||
|
||||
$ easy_install pelican
|
||||
|
||||
While the above is the simplest method, the recommended approach is to create
|
||||
a virtual environment for Pelican via virtualenv_ and virtualenvwrapper_ before
|
||||
installing Pelican. Assuming you've followed the virtualenvwrapper
|
||||
`installation <http://virtualenvwrapper.readthedocs.org/en/latest/install.html>`_
|
||||
and `shell configuration
|
||||
<http://virtualenvwrapper.readthedocs.org/en/latest/install.html#shell-startup-file>`_
|
||||
steps, you can then open a new terminal session and create a new virtual
|
||||
environment for Pelican::
|
||||
|
||||
$ mkvirtualenv pelican
|
||||
|
||||
Once the virtual environment has been created and activated, Pelican can be
|
||||
be installed via ``pip`` or ``easy_install`` as noted above. Alternatively, if
|
||||
you have the project source, you can install Pelican using the distutils
|
||||
method::
|
||||
|
||||
$ cd path-to-Pelican-source
|
||||
$ python setup.py install
|
||||
|
||||
If you have Git installed and prefer to install the latest bleeding-edge
|
||||
version of Pelican rather than a stable release, use the following command::
|
||||
|
||||
$ pip install -e git://github.com/getpelican/pelican#egg=pelican
|
||||
|
||||
If you plan on using Markdown as a markup format, you'll need to install the
|
||||
Markdown library as well::
|
||||
|
||||
$ pip install Markdown
|
||||
|
||||
Upgrading
|
||||
---------
|
||||
|
||||
If you installed a stable Pelican release via ``pip`` or ``easy_install`` and
|
||||
wish to upgrade to the latest stable release, you can do so by adding
|
||||
``--upgrade`` to the relevant command. For pip, that would be::
|
||||
|
||||
$ pip install --upgrade pelican
|
||||
|
||||
If you installed Pelican via distutils or the bleeding-edge method, simply
|
||||
perform the same step to install the most recent version.
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
At this time, Pelican is dependent on the following Python packages:
|
||||
|
||||
* feedgenerator, to generate the Atom feeds
|
||||
* jinja2, for templating support
|
||||
* docutils, for supporting reStructuredText as an input format
|
||||
|
||||
If you're not using Python 2.7, you will also need the ``argparse`` package.
|
||||
|
||||
Optionally:
|
||||
|
||||
* pygments, for syntax highlighting
|
||||
* Markdown, for supporting Markdown as an input format
|
||||
* Typogrify, for typographical enhancements
|
||||
|
||||
Kickstart a blog
|
||||
================
|
||||
|
||||
Following is a brief tutorial for those who want to get started right away.
|
||||
We're going to assume that virtualenv_ and virtualenvwrapper_ are installed and
|
||||
configured; if you've installed Pelican outside of a virtual environment,
|
||||
you can skip to the ``pelican-quickstart`` command. Let's first create a new
|
||||
virtual environment and install Pelican into it::
|
||||
|
||||
$ mkvirtualenv pelican
|
||||
$ pip install pelican Markdown
|
||||
|
||||
Next we'll create a directory to house our site content and configuration files,
|
||||
which can be located any place you prefer, and associate this new project with
|
||||
the currently-active virtual environment::
|
||||
|
||||
$ mkdir ~/code/yoursitename
|
||||
$ cd ~/code/yoursitename
|
||||
$ setvirtualenvproject
|
||||
|
||||
Now we can run the ``pelican-quickstart`` command, which will ask some questions
|
||||
about your site::
|
||||
|
||||
$ pelican-quickstart
|
||||
|
||||
Once you finish answering all the questions, you can begin adding content to the
|
||||
*content* folder that has been created for you. (See *Writing articles using
|
||||
Pelican* section below for more information about how to format your content.)
|
||||
Once you have some content to generate, you can convert it to HTML via the
|
||||
following command::
|
||||
|
||||
$ make html
|
||||
|
||||
If you'd prefer to have Pelican automatically regenerate your site every time a
|
||||
change is detected (handy when testing locally), use the following command
|
||||
instead::
|
||||
|
||||
$ make regenerate
|
||||
|
||||
To serve the site so it can be previewed in your browser at
|
||||
http://localhost:8000::
|
||||
|
||||
$ make serve
|
||||
|
||||
Normally you would need to run ``make regenerate`` and ``make serve`` in two
|
||||
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
|
||||
|
||||
When you're ready to publish your site, you can upload it via the method(s) you
|
||||
chose during the ``pelican-quickstart`` questionnaire. For this example, we'll
|
||||
use rsync over ssh::
|
||||
|
||||
$ make rsync_upload
|
||||
|
||||
That's it! Your site should now be live.
|
||||
|
||||
Writing articles using Pelican
|
||||
==============================
|
||||
|
||||
File metadata
|
||||
--------------
|
||||
|
||||
Pelican tries to be smart enough to get the information it needs from the
|
||||
file system (for instance, about the category of your articles), but some
|
||||
information you need to provide in the form of metadata inside your files.
|
||||
|
||||
You can provide this metadata in reStructuredText text files via the
|
||||
following syntax (give your file the ``.rst`` extension)::
|
||||
|
||||
My super title
|
||||
##############
|
||||
|
||||
:date: 2010-10-03 10:20
|
||||
:tags: thats, awesome
|
||||
:category: yeah
|
||||
:author: Alexis Metaireau
|
||||
|
||||
Pelican implements an extension of reStructuredText to enable support for the
|
||||
``abbr`` HTML tag. To use it, write something like this in your post::
|
||||
|
||||
This will be turned into :abbr:`HTML (HyperText Markup Language)`.
|
||||
|
||||
You can also use Markdown syntax (with a file ending in ``.md``).
|
||||
Markdown generation will not work until you explicitly install the ``Markdown``
|
||||
package, which can be done via ``pip install Markdown``. Metadata syntax for
|
||||
Markdown posts should follow this pattern::
|
||||
|
||||
Date: 2010-12-03
|
||||
Title: My super title
|
||||
Tags: thats, awesome
|
||||
Slug: my-super-post
|
||||
|
||||
This is the content of my super blog post.
|
||||
|
||||
Note that, aside from the title, none of this metadata is mandatory: if the date
|
||||
is not specified, Pelican will rely on the file's "mtime" timestamp, and the
|
||||
category can be determined by the directory in which the file resides. For
|
||||
example, a file located at ``python/foobar/myfoobar.rst`` will have a category of
|
||||
``foobar``.
|
||||
|
||||
Generate your blog
|
||||
------------------
|
||||
|
||||
The ``make`` shortcut commands mentioned in the ``Kickstart a blog`` section
|
||||
are mostly wrappers around the ``pelican`` command that generates the HTML from
|
||||
the content. The ``pelican`` command can also be run directly::
|
||||
|
||||
$ pelican /path/to/your/content/ [-s path/to/your/settings.py]
|
||||
|
||||
The above command will generate your weblog and save it in the ``output/``
|
||||
folder, using the default theme to produce a simple site. The default theme is
|
||||
simple HTML without styling and is provided so folks may use it as a basis for
|
||||
creating their own themes.
|
||||
|
||||
Pelican has other command-line switches available. Have a look at the help to
|
||||
see all the options you can use::
|
||||
|
||||
$ pelican --help
|
||||
|
||||
Auto-reload
|
||||
-----------
|
||||
|
||||
It's possible to tell Pelican to watch for your modifications, instead of
|
||||
manually re-running it every time you want to see your changes. To enable this,
|
||||
run the ``pelican`` command with the ``-r`` or ``--autoreload`` option.
|
||||
|
||||
Pages
|
||||
-----
|
||||
|
||||
If you create a folder named ``pages``, all the files in it will be used to
|
||||
generate static pages.
|
||||
|
||||
Then, use the ``DISPLAY_PAGES_ON_MENU`` setting, which will add all the pages to
|
||||
the menu.
|
||||
|
||||
If you want to exclude any pages from being linked to or listed in the menu
|
||||
then add a ``status: hidden`` attribute to its metadata. This is useful for
|
||||
things like making error pages that fit the generated theme of your site.
|
||||
|
||||
Importing an existing blog
|
||||
--------------------------
|
||||
|
||||
It is possible to import your blog from Dotclear, WordPress, and RSS feeds using
|
||||
a simple script. See :ref:`import`.
|
||||
|
||||
Translations
|
||||
------------
|
||||
|
||||
It is possible to translate articles. To do so, you need to add a ``lang`` meta
|
||||
attribute to your articles/pages and set a ``DEFAULT_LANG`` setting (which is
|
||||
English [en] by default). With those settings in place, only articles with the
|
||||
default language will be listed, and each article will be accompanied by a list
|
||||
of available translations for that article.
|
||||
|
||||
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
|
||||
metadata; if not set explicitly, Pelican will auto-generate the slug from the
|
||||
title of the article.
|
||||
|
||||
Here is an example of two articles, one in English and the other in French.
|
||||
|
||||
The English article::
|
||||
|
||||
Foobar is not dead
|
||||
##################
|
||||
|
||||
:slug: foobar-is-not-dead
|
||||
:lang: en
|
||||
|
||||
That's true, foobar is still alive!
|
||||
|
||||
And the French version::
|
||||
|
||||
Foobar n'est pas mort !
|
||||
#######################
|
||||
|
||||
:slug: foobar-is-not-dead
|
||||
:lang: fr
|
||||
|
||||
Oui oui, foobar est toujours vivant !
|
||||
|
||||
Post content quality notwithstanding, you can see that only item in common
|
||||
between the two articles is the slug, which is functioning here as an
|
||||
identifier. If you'd rather not explicitly define the slug this way, you must
|
||||
then instead ensure that the translated article titles are identical, since the
|
||||
slug will be auto-generated from the article title.
|
||||
|
||||
Syntax highlighting
|
||||
---------------------
|
||||
|
||||
Pelican is able to provide colorized syntax highlighting for your code blocks.
|
||||
To do so, you have to use the following conventions (you need to put this in
|
||||
your content files).
|
||||
|
||||
For RestructuredText, use the code-block directive::
|
||||
|
||||
.. code-block:: identifier
|
||||
|
||||
<indented code block goes here>
|
||||
|
||||
For Markdown, include the language identifier just above the code block,
|
||||
indenting both the identifier and code::
|
||||
|
||||
:::identifier
|
||||
<code goes here>
|
||||
|
||||
The specified identifier (e.g. ``python``, ``ruby``) should be one that
|
||||
appears on the `list of available lexers <http://pygments.org/docs/lexers/>`_.
|
||||
|
||||
Publishing drafts
|
||||
-----------------
|
||||
|
||||
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
|
||||
listed on the index page nor on any category page.
|
||||
|
||||
Viewing the generated files
|
||||
---------------------------
|
||||
|
||||
The files generated by Pelican are static files, so you don't actually need
|
||||
anything special to see what's happening with the generated files.
|
||||
|
||||
You can either use your browser to open the files on your disk::
|
||||
|
||||
$ firefox output/index.html
|
||||
|
||||
Or run a simple web server using Python::
|
||||
|
||||
cd output && python -m SimpleHTTPServer
|
||||
|
||||
.. _virtualenv: http://www.virtualenv.org/
|
||||
.. _virtualenvwrapper: http://www.doughellmann.com/projects/virtualenvwrapper/
|
||||
|
|
@ -1,74 +1,115 @@
|
|||
.. _import:
|
||||
|
||||
=================================
|
||||
Import from other blog software
|
||||
=================================
|
||||
Importing an existing site
|
||||
##########################
|
||||
|
||||
Description
|
||||
===========
|
||||
|
||||
``pelican-import`` is a command line tool for converting articles from other
|
||||
software to ReStructuredText. The supported formats are:
|
||||
``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
|
||||
- Dotclear export
|
||||
- Posterous API
|
||||
- Tumblr API
|
||||
- RSS/Atom feed
|
||||
|
||||
The conversion from HTML to reStructuredText relies on `pandoc
|
||||
<http://johnmacfarlane.net/pandoc/>`_. For Dotclear, if the source posts are
|
||||
written with Markdown syntax, they will not be converted (as Pelican also
|
||||
supports Markdown).
|
||||
The conversion from HTML to reStructuredText or Markdown relies on `Pandoc`_.
|
||||
For Dotclear, if the source posts are written with Markdown syntax, they will
|
||||
not be converted (as Pelican also supports Markdown).
|
||||
|
||||
|
||||
Dependencies
|
||||
""""""""""""
|
||||
============
|
||||
|
||||
``pelican-import`` has two dependencies not required by the rest of pelican:
|
||||
``pelican-import`` has some dependencies not required by the rest of Pelican:
|
||||
|
||||
- BeautifulSoup
|
||||
- pandoc
|
||||
- *BeautifulSoup4* and *lxml*, for WordPress and Dotclear import. Can be installed like
|
||||
any other Python package (``pip install BeautifulSoup4 lxml``).
|
||||
- *Feedparser*, for feed import (``pip install feedparser``).
|
||||
- *Pandoc*, see the `Pandoc site`_ for installation instructions on your
|
||||
operating system.
|
||||
|
||||
BeatifulSoup can be installed like any other Python package::
|
||||
|
||||
$ pip install BeautifulSoup
|
||||
|
||||
For pandoc, install a package for your operating system from the
|
||||
`pandoc site <http://johnmacfarlane.net/pandoc/installing.html>`_.
|
||||
.. _Pandoc: http://johnmacfarlane.net/pandoc/
|
||||
.. _Pandoc site: http://johnmacfarlane.net/pandoc/installing.html
|
||||
|
||||
|
||||
Usage
|
||||
"""""
|
||||
=====
|
||||
|
||||
| pelican-import [-h] [--wpfile] [--dotclear] [--feed] [-o OUTPUT]
|
||||
| [-m MARKUP][--dir-cat]
|
||||
| input
|
||||
::
|
||||
|
||||
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]
|
||||
input|api_token|api_key
|
||||
|
||||
Positional arguments
|
||||
--------------------
|
||||
============= ============================================================================
|
||||
``input`` The input file to read
|
||||
``api_token`` (Posterous only) api_token can be obtained from http://posterous.com/api/
|
||||
``api_key`` (Tumblr only) api_key can be obtained from http://www.tumblr.com/oauth/apps
|
||||
============= ============================================================================
|
||||
|
||||
Optional arguments
|
||||
""""""""""""""""""
|
||||
------------------
|
||||
|
||||
-h, --help show this help message and exit
|
||||
--wpfile Wordpress XML export
|
||||
--dotclear Dotclear export
|
||||
--feed Feed to parse
|
||||
-h, --help Show this help message and exit
|
||||
--wpfile WordPress XML export (default: False)
|
||||
--dotclear Dotclear export (default: False)
|
||||
--posterous Posterous API (default: False)
|
||||
--tumblr Tumblr API (default: False)
|
||||
--feed Feed to parse (default: False)
|
||||
-o OUTPUT, --output OUTPUT
|
||||
Output path
|
||||
-m MARKUP Output markup
|
||||
Output path (default: output)
|
||||
-m MARKUP, --markup MARKUP
|
||||
Output markup format (supports rst & markdown)
|
||||
(default: rst)
|
||||
--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.
|
||||
--strip-raw Strip raw HTML code that can't be converted to markup
|
||||
such as flash embeds or iframes (wordpress import
|
||||
only) (default: False)
|
||||
--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:
|
||||
False)
|
||||
-e EMAIL, --email=EMAIL
|
||||
Email used to authenticate Posterous API
|
||||
-p PASSWORD, --password=PASSWORD
|
||||
Password used to authenticate Posterous API
|
||||
-b BLOGNAME, --blogname=BLOGNAME
|
||||
Blog name used in Tumblr API
|
||||
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
for WordPress::
|
||||
For WordPress::
|
||||
|
||||
$ pelican-import --wpfile -o ~/output ~/posts.xml
|
||||
|
||||
for Dotclear::
|
||||
For Dotclear::
|
||||
|
||||
$ pelican-import --dotclear -o ~/output ~/backup.txt
|
||||
|
||||
for Posterous::
|
||||
|
||||
$ pelican-import --posterous -o ~/output --email=<email_address> --password=<password> <api_token>
|
||||
|
||||
For Tumblr::
|
||||
|
||||
$ pelican-import --tumblr -o ~/output --blogname=<blogname> <api_token>
|
||||
|
||||
Tests
|
||||
=====
|
||||
|
||||
To test the module, one can use sample files:
|
||||
|
||||
- for Wordpress: http://wpcandy.com/made/the-sample-post-collection
|
||||
- for WordPress: http://wpcandy.com/made/the-sample-post-collection
|
||||
- for Dotclear: http://themes.dotaddict.org/files/public/downloads/lorem-backup.txt
|
||||
|
|
|
|||
|
|
@ -1,31 +1,41 @@
|
|||
Pelican
|
||||
=======
|
||||
Pelican |release|
|
||||
=================
|
||||
|
||||
Pelican is a static site generator, written in Python_.
|
||||
|
||||
* Write your weblog entries directly with your editor of choice (vim!)
|
||||
in reStructuredText_ or Markdown_
|
||||
* Includes a simple CLI tool to (re)generate the weblog
|
||||
* Easy to interface with DVCSes and web hooks
|
||||
.. ifconfig:: release.endswith('.dev')
|
||||
|
||||
.. warning::
|
||||
|
||||
This documentation is for the version of Pelican currently under development.
|
||||
Were you looking for version |last_stable| documentation?
|
||||
|
||||
|
||||
Pelican is a static site generator, written in Python_. Highlights include:
|
||||
|
||||
* Write your content directly with your editor of choice
|
||||
in reStructuredText_ or Markdown_ formats
|
||||
* Includes a simple CLI tool to (re)generate your site
|
||||
* Easy to interface with distributed version control systems and web hooks
|
||||
* Completely static output is easy to host anywhere
|
||||
|
||||
Ready to get started? Check out the :doc:`Quickstart<quickstart>` guide.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
Pelican currently supports:
|
||||
Pelican |version| currently supports:
|
||||
|
||||
* Blog articles and pages
|
||||
* Comments, via an external service (Disqus). (Please note that while
|
||||
useful, Disqus is an external service, and thus the comment data will be
|
||||
somewhat outside of your control and potentially subject to data loss.)
|
||||
* Articles (e.g., blog posts) and pages (e.g., "About", "Projects", "Contact")
|
||||
* Comments, via an external service (Disqus). If you prefer to have more
|
||||
control over your comment data, self-hosted comments are another option.
|
||||
Check out the `Pelican Plugins`_ repository for more details.
|
||||
* Theming support (themes are created using Jinja2_ templates)
|
||||
* PDF generation of the articles/pages (optional)
|
||||
* Publication of articles in multiple languages
|
||||
* Atom/RSS feeds
|
||||
* Code syntax highlighting
|
||||
* Compilation of `LESS CSS`_ (optional)
|
||||
* Import from WordPress, Dotclear, or RSS feeds
|
||||
* Integration with external tools: Twitter, Google Analytics, etc. (optional)
|
||||
* Fast rebuild times thanks to content caching and selective output writing
|
||||
|
||||
Why the name "Pelican"?
|
||||
-----------------------
|
||||
|
|
@ -37,35 +47,30 @@ Source code
|
|||
|
||||
You can access the source code at: https://github.com/getpelican/pelican
|
||||
|
||||
Feedback / Contact us
|
||||
---------------------
|
||||
How to get help, contribute, or provide feedback
|
||||
------------------------------------------------
|
||||
|
||||
If you want to see new features in Pelican, don't hesitate to offer suggestions,
|
||||
clone the repository, etc. There are many ways to :doc:`contribute<contribute>`.
|
||||
That's open source, dude!
|
||||
|
||||
Send a message to "authors at getpelican dot com" with any requests/feedback! You
|
||||
can also join the team at `#pelican on Freenode`_ (or if you don't have an IRC
|
||||
client handy, use the webchat_ for quick feedback.
|
||||
See our :doc:`feedback and contribution submission guidelines <contribute>`.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
A French version of the documentation is available at :doc:`fr/index`.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
getting_started
|
||||
quickstart
|
||||
install
|
||||
content
|
||||
publish
|
||||
settings
|
||||
themes
|
||||
plugins
|
||||
internals
|
||||
pelican-themes
|
||||
importer
|
||||
faq
|
||||
tips
|
||||
contribute
|
||||
internals
|
||||
report
|
||||
changelog
|
||||
|
||||
|
|
@ -75,8 +80,6 @@ A French version of the documentation is available at :doc:`fr/index`.
|
|||
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
|
||||
.. _Markdown: http://daringfireball.net/projects/markdown/
|
||||
.. _Jinja2: http://jinja.pocoo.org/
|
||||
.. _`LESS CSS`: http://lesscss.org/
|
||||
.. _`Pelican documentation`: http://docs.getpelican.com/latest/
|
||||
.. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html
|
||||
.. _`#pelican on Freenode`: irc://irc.freenode.net/pelican
|
||||
.. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4
|
||||
.. _`Pelican plugins`: https://github.com/getpelican/pelican-plugins
|
||||
|
|
|
|||
117
docs/install.rst
Normal file
117
docs/install.rst
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
Installing Pelican
|
||||
##################
|
||||
|
||||
Pelican currently runs best on Python 2.7.x; earlier versions of Python are
|
||||
not supported. There is provisional support for Python 3.3+, although there may
|
||||
be rough edges, particularly with regards to optional 3rd-party components.
|
||||
|
||||
You can install Pelican via several different methods. The simplest is via
|
||||
`pip <http://www.pip-installer.org/>`_::
|
||||
|
||||
pip install pelican
|
||||
|
||||
(Keep in mind that operating systems will often require you to prefix the above
|
||||
command with ``sudo`` in order to install Pelican system-wide.)
|
||||
|
||||
While the above is the simplest method, the recommended approach is to create
|
||||
a virtual environment for Pelican via virtualenv_ before installing Pelican.
|
||||
Assuming you have virtualenv_ installed, you can then open a new terminal
|
||||
session and create a new virtual environment for Pelican::
|
||||
|
||||
virtualenv ~/virtualenvs/pelican
|
||||
cd ~/virtualenvs/pelican
|
||||
source bin/activate
|
||||
|
||||
Once the virtual environment has been created and activated, Pelican can be
|
||||
be installed via ``pip install pelican`` as noted above. Alternatively, if
|
||||
you have the project source, you can install Pelican using the distutils
|
||||
method::
|
||||
|
||||
cd path-to-Pelican-source
|
||||
python setup.py install
|
||||
|
||||
If you have Git installed and prefer to install the latest bleeding-edge
|
||||
version of Pelican rather than a stable release, use the following command::
|
||||
|
||||
pip install -e "git+https://github.com/getpelican/pelican.git#egg=pelican"
|
||||
|
||||
Once Pelican is installed, you can run ``pelican --help`` to see basic usage
|
||||
options. For more detail, refer to the :doc:`Publish<publish>` section.
|
||||
|
||||
Optional packages
|
||||
-----------------
|
||||
|
||||
If you plan on using `Markdown <http://pypi.python.org/pypi/Markdown>`_ as a
|
||||
markup format, you'll need to install the Markdown library::
|
||||
|
||||
pip install Markdown
|
||||
|
||||
Typographical enhancements can be enabled in your settings file, but first the
|
||||
requisite `Typogrify <http://pypi.python.org/pypi/typogrify>`_ library must be
|
||||
installed::
|
||||
|
||||
pip install typogrify
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
When Pelican is installed, the following dependent Python packages should be
|
||||
automatically installed without any action on your part:
|
||||
|
||||
* `feedgenerator <http://pypi.python.org/pypi/feedgenerator>`_, to generate the
|
||||
Atom feeds
|
||||
* `jinja2 <http://pypi.python.org/pypi/Jinja2>`_, for templating support
|
||||
* `pygments <http://pypi.python.org/pypi/Pygments>`_, for syntax highlighting
|
||||
* `docutils <http://pypi.python.org/pypi/docutils>`_, for supporting
|
||||
reStructuredText as an input format
|
||||
* `pytz <http://pypi.python.org/pypi/pytz>`_, for timezone definitions
|
||||
* `blinker <http://pypi.python.org/pypi/blinker>`_, an object-to-object and
|
||||
broadcast signaling system
|
||||
* `unidecode <http://pypi.python.org/pypi/Unidecode>`_, for ASCII
|
||||
transliterations of Unicode text
|
||||
* `six <http://pypi.python.org/pypi/six>`_, for Python 2 and 3 compatibility
|
||||
utilities
|
||||
* `MarkupSafe <http://pypi.python.org/pypi/MarkupSafe>`_, for a markup safe
|
||||
string implementation
|
||||
* `python-dateutil <https://pypi.python.org/pypi/python-dateutil>`_, to read
|
||||
the date metadata
|
||||
|
||||
Upgrading
|
||||
---------
|
||||
|
||||
If you installed a stable Pelican release via ``pip`` or ``easy_install`` and
|
||||
wish to upgrade to the latest stable release, you can do so by adding
|
||||
``--upgrade`` to the relevant command. For pip, that would be::
|
||||
|
||||
pip install --upgrade pelican
|
||||
|
||||
If you installed Pelican via distutils or the bleeding-edge method, simply
|
||||
perform the same step to install the most recent version.
|
||||
|
||||
Kickstart your site
|
||||
===================
|
||||
|
||||
Once Pelican has been installed, you can create a skeleton project via the
|
||||
``pelican-quickstart`` command, which begins by asking some questions about
|
||||
your site::
|
||||
|
||||
pelican-quickstart
|
||||
|
||||
Once you finish answering all the questions, your project will consist of the
|
||||
following hierarchy (except for *pages* — shown in parentheses below — which you
|
||||
can optionally add yourself if you plan to create non-chronological content)::
|
||||
|
||||
yourproject/
|
||||
├── content
|
||||
│ └── (pages)
|
||||
├── output
|
||||
├── develop_server.sh
|
||||
├── fabfile.py
|
||||
├── Makefile
|
||||
├── pelicanconf.py # Main settings file
|
||||
└── publishconf.py # Settings to use when ready to publish
|
||||
|
||||
The next step is to begin to adding content to the *content* folder that has
|
||||
been created for you.
|
||||
|
||||
.. _virtualenv: http://www.virtualenv.org/
|
||||
|
|
@ -12,8 +12,8 @@ original author wrote with some software design information.
|
|||
Overall structure
|
||||
=================
|
||||
|
||||
What Pelican does is take a list of files and process them into some
|
||||
sort of output. Usually, the input files are reStructuredText and Markdown
|
||||
What Pelican does is take a list of files and process them into some sort of
|
||||
output. Usually, the input files are reStructuredText and Markdown
|
||||
files, and the output is a blog, but both input and output can be anything you
|
||||
want.
|
||||
|
||||
|
|
@ -23,9 +23,9 @@ The logic is separated into different classes and concepts:
|
|||
on. Since those operations are commonly used, the object is created once and
|
||||
then passed to the generators.
|
||||
|
||||
* **Readers** are used to read from various formats (Markdown and
|
||||
reStructuredText for now, but the system is extensible). Given a file, they return
|
||||
metadata (author, tags, category, etc.) and content (HTML-formatted).
|
||||
* **Readers** are used to read from various formats (HTML, Markdown and
|
||||
reStructuredText for now, but the system is extensible). Given a file, they
|
||||
return metadata (author, tags, category, etc.) and content (HTML-formatted).
|
||||
|
||||
* **Generators** generate the different outputs. For instance, Pelican comes with
|
||||
``ArticlesGenerator`` and ``PageGenerator``. Given a configuration, they can do
|
||||
|
|
@ -44,22 +44,20 @@ method that returns HTML content and some metadata.
|
|||
|
||||
Take a look at the Markdown reader::
|
||||
|
||||
class MarkdownReader(Reader):
|
||||
class MarkdownReader(BaseReader):
|
||||
enabled = bool(Markdown)
|
||||
|
||||
def read(self, filename):
|
||||
def read(self, source_path):
|
||||
"""Parse content and metadata of markdown files"""
|
||||
text = open(filename)
|
||||
text = pelican_open(source_path)
|
||||
md = Markdown(extensions = ['meta', 'codehilite'])
|
||||
content = md.convert(text)
|
||||
|
||||
metadata = {}
|
||||
for name, value in md.Meta.items():
|
||||
if name in _METADATA_FIELDS:
|
||||
meta = _METADATA_FIELDS[name](value[0])
|
||||
else:
|
||||
meta = value[0]
|
||||
metadata[name.lower()] = meta
|
||||
name = name.lower()
|
||||
meta = self.process_metadata(name, value[0])
|
||||
metadata[name] = meta
|
||||
return content, metadata
|
||||
|
||||
Simple, isn't it?
|
||||
|
|
|
|||
|
|
@ -153,12 +153,3 @@ The ``--install``, ``--remove`` and ``--symlink`` option are not mutually exclus
|
|||
--verbose
|
||||
|
||||
In this example, the theme ``notmyidea-cms`` is replaced by the theme ``notmyidea-cms-fr``
|
||||
|
||||
|
||||
|
||||
|
||||
See also
|
||||
========
|
||||
|
||||
- http://docs.notmyidea.org/alexis/pelican/
|
||||
- ``/usr/share/doc/pelican/`` if you have installed Pelican using the `APT repository <http://skami18.github.com/pelican-packages/>`_
|
||||
|
|
|
|||
383
docs/plugins.rst
383
docs/plugins.rst
|
|
@ -3,37 +3,55 @@
|
|||
Plugins
|
||||
#######
|
||||
|
||||
Since version 3.0, Pelican manages plugins. Plugins are a way to add features
|
||||
to Pelican without having to directly hack Pelican code.
|
||||
|
||||
Pelican is shipped with a set of core plugins, but you can easily implement
|
||||
your own (and this page describes how).
|
||||
Beginning with version 3.0, Pelican supports plugins. Plugins are a way to add
|
||||
features to Pelican without having to directly modify the Pelican core.
|
||||
|
||||
How to use plugins
|
||||
==================
|
||||
|
||||
To load plugins, you have to specify them in your settings file. You have two
|
||||
ways to do so.
|
||||
Either by specifying strings with the path to the callables::
|
||||
To load plugins, you have to specify them in your settings file. There are two
|
||||
ways to do so. The first method is to specify strings with the path to the
|
||||
callables::
|
||||
|
||||
PLUGINS = ['pelican.plugins.gravatar',]
|
||||
PLUGINS = ['package.myplugin',]
|
||||
|
||||
Or by importing them and adding them to the list::
|
||||
Alternatively, another method is to import them and add them to the list::
|
||||
|
||||
from pelican.plugins import gravatar
|
||||
PLUGINS = [gravatar, ]
|
||||
from package import myplugin
|
||||
PLUGINS = [myplugin,]
|
||||
|
||||
If your plugins are not in an importable path, you can specify a ``PLUGIN_PATH``
|
||||
in the settings::
|
||||
.. note::
|
||||
|
||||
PLUGIN_PATH = "plugins"
|
||||
PLUGINS = ["list", "of", "plugins"]
|
||||
When experimenting with different plugins (especially the ones that
|
||||
deal with metadata and content) caching may interfere and the
|
||||
changes may not be visible. In such cases disable caching with
|
||||
``LOAD_CONTENT_CACHE = False`` or use the ``--ignore-cache``
|
||||
command-line switch.
|
||||
|
||||
If your plugins are not in an importable path, you can specify a list of paths
|
||||
via the ``PLUGIN_PATHS`` setting. As shown in the following example, paths in
|
||||
the ``PLUGIN_PATHS`` list can be absolute or relative to the settings file::
|
||||
|
||||
PLUGIN_PATHS = ["plugins", "/srv/pelican/plugins"]
|
||||
PLUGINS = ["assets", "liquid_tags", "sitemap"]
|
||||
|
||||
Where to find plugins
|
||||
=====================
|
||||
|
||||
We maintain a separate repository of plugins for people to share and use.
|
||||
Please visit the `pelican-plugins`_ repository for a list of available plugins.
|
||||
|
||||
.. _pelican-plugins: https://github.com/getpelican/pelican-plugins
|
||||
|
||||
Please note that while we do our best to review and maintain these plugins,
|
||||
they are submitted by the Pelican community and thus may have varying levels of
|
||||
support and interoperability.
|
||||
|
||||
How to create plugins
|
||||
=====================
|
||||
|
||||
Plugins are based on the concept of signals. Pelican sends signals, and plugins
|
||||
subscribe to those signals. The list of signals are defined in a following
|
||||
subscribe to those signals. The list of signals are defined in a subsequent
|
||||
section.
|
||||
|
||||
The only rule to follow for plugins is to define a ``register`` callable, in
|
||||
|
|
@ -47,227 +65,160 @@ which you map the signals to your plugin logic. Let's take a simple example::
|
|||
def register():
|
||||
signals.initialized.connect(test)
|
||||
|
||||
.. note::
|
||||
|
||||
Signal receivers are weakly-referenced and thus must not be defined within
|
||||
your ``register`` callable or they will be garbage-collected before the
|
||||
signal is emitted.
|
||||
|
||||
List of signals
|
||||
===============
|
||||
|
||||
Here is the list of currently implemented signals:
|
||||
|
||||
========================= ============================ ===========================================================================
|
||||
Signal Arguments Description
|
||||
========================= ============================ ===========================================================================
|
||||
initialized pelican object
|
||||
finalized pelican object invoked after all the generators are executed and just before pelican exits
|
||||
usefull for custom post processing actions, such as:
|
||||
- minifying js/css assets.
|
||||
- notify/ping search engines with an updated sitemap.
|
||||
article_generate_context article_generator, metadata
|
||||
article_generator_init article_generator invoked in the ArticlesGenerator.__init__
|
||||
get_generators generators invoked in Pelican.get_generator_classes,
|
||||
can return a Generator, or several
|
||||
generator in a tuple or in a list.
|
||||
pages_generate_context pages_generator, metadata
|
||||
pages_generator_init pages_generator invoked in the PagesGenerator.__init__
|
||||
========================= ============================ ===========================================================================
|
||||
================================= ============================ ===========================================================================
|
||||
Signal Arguments Description
|
||||
================================= ============================ ===========================================================================
|
||||
initialized pelican object
|
||||
finalized pelican object invoked after all the generators are executed and just before pelican exits
|
||||
useful for custom post processing actions, such as:
|
||||
- minifying js/css assets.
|
||||
- notify/ping search engines with an updated sitemap.
|
||||
generator_init generator invoked in the Generator.__init__
|
||||
readers_init readers invoked in the Readers.__init__
|
||||
article_generator_context article_generator, metadata
|
||||
article_generator_preread article_generator invoked before a article is read in ArticlesGenerator.generate_context;
|
||||
use if code needs to do something before every article is parsed
|
||||
article_generator_init article_generator invoked in the ArticlesGenerator.__init__
|
||||
article_generator_pretaxonomy article_generator invoked before categories and tags lists are created
|
||||
useful when e.g. modifying the list of articles to be generated
|
||||
so that removed articles are not leaked in categories or tags
|
||||
article_generator_finalized article_generator invoked at the end of ArticlesGenerator.generate_context
|
||||
article_generator_write_article article_generator, content invoked before writing each article, the article is passed as content
|
||||
article_writer_finalized article_generator, writer invoked after all articles and related pages have been written, but before
|
||||
the article generator is closed.
|
||||
get_generators pelican object invoked in Pelican.get_generator_classes,
|
||||
can return a Generator, or several
|
||||
generators in a tuple or in a list.
|
||||
get_writer pelican object invoked in Pelican.get_writer,
|
||||
can return a custom Writer.
|
||||
page_generator_context page_generator, metadata
|
||||
page_generator_preread page_generator invoked before a page is read in PageGenerator.generate_context;
|
||||
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
|
||||
static_generator_context static_generator, metadata
|
||||
static_generator_preread static_generator invoked before a static file is read in StaticGenerator.generate_context;
|
||||
use if code needs to do something before every static file is added to the
|
||||
staticfiles list.
|
||||
static_generator_init static_generator invoked in the StaticGenerator.__init__
|
||||
static_generator_finalized static_generator invoked at the end of StaticGenerator.generate_context
|
||||
content_object_init content_object invoked at the end of Content.__init__ (see note below)
|
||||
content_written path, context invoked each time a content file is written.
|
||||
feed_written path, context, feed invoked each time a feed file is written.
|
||||
================================= ============================ ===========================================================================
|
||||
|
||||
The list is currently small, don't hesitate to add signals and make a pull
|
||||
The list is currently small, so don't hesitate to add signals and make a pull
|
||||
request if you need them!
|
||||
|
||||
.. note::
|
||||
|
||||
The signal ``content_object_init`` can send different type of object as
|
||||
argument. If you want to register only one type of object then you will
|
||||
.. note::
|
||||
|
||||
The signal ``content_object_init`` can send a different type of object as
|
||||
the argument. If you want to register only one type of object then you will
|
||||
need to specify the sender when you are connecting to the signal.
|
||||
|
||||
|
||||
::
|
||||
|
||||
|
||||
from pelican import signals
|
||||
from pelican import contents
|
||||
|
||||
|
||||
def test(sender, instance):
|
||||
print "%s : %s content initialized !!" % (sender, instance)
|
||||
|
||||
|
||||
def register():
|
||||
signals.content_object_init.connect(test, sender=contents.Article)
|
||||
|
||||
|
||||
|
||||
List of plugins
|
||||
===============
|
||||
|
||||
The following plugins are currently included with Pelican under ``pelican.plugins``:
|
||||
|
||||
* `GitHub activity`_
|
||||
* `Global license`_
|
||||
* `Gravatar`_
|
||||
* `HTML tags for reStructuredText`_
|
||||
* `Related posts`_
|
||||
* `Sitemap`_
|
||||
|
||||
Ideas for plugins that haven't been written yet:
|
||||
|
||||
* Tag cloud
|
||||
* Translation
|
||||
|
||||
Plugin descriptions
|
||||
===================
|
||||
|
||||
GitHub activity
|
||||
---------------
|
||||
|
||||
This plugin makes use of the ``feedparser`` library that you'll need to
|
||||
install.
|
||||
|
||||
Set the ``GITHUB_ACTIVITY_FEED`` parameter to your GitHub activity feed.
|
||||
For example, my setting would look like::
|
||||
|
||||
GITHUB_ACTIVITY_FEED = 'https://github.com/kpanic.atom'
|
||||
|
||||
On the templates side, you just have to iterate over the ``github_activity``
|
||||
variable, as in the example::
|
||||
|
||||
{% if GITHUB_ACTIVITY_FEED %}
|
||||
<div class="social">
|
||||
<h2>Github Activity</h2>
|
||||
<ul>
|
||||
|
||||
{% for entry in github_activity %}
|
||||
<li><b>{{ entry[0] }}</b><br /> {{ entry[1] }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div><!-- /.github_activity -->
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
``github_activity`` is a list of lists. The first element is the title
|
||||
and the second element is the raw HTML from GitHub.
|
||||
|
||||
Global license
|
||||
--------------
|
||||
|
||||
This plugin allows you to define a LICENSE setting and adds the contents of that
|
||||
license variable to the article's context, making that variable available to use
|
||||
from within your theme's templates.
|
||||
|
||||
Gravatar
|
||||
--------
|
||||
|
||||
This plugin assigns the ``author_gravatar`` variable to the Gravatar URL and
|
||||
makes the variable available within the article's context. You can add
|
||||
AUTHOR_EMAIL to your settings file to define the default author's email
|
||||
address. Obviously, that email address must be associated with a Gravatar
|
||||
account.
|
||||
|
||||
Alternatively, you can provide an email address from within article metadata::
|
||||
|
||||
:email: john.doe@example.com
|
||||
|
||||
If the email address is defined via at least one of the two methods above,
|
||||
the ``author_gravatar`` variable is added to the article's context.
|
||||
|
||||
HTML tags for reStructuredText
|
||||
------------------------------
|
||||
|
||||
This plugin allows you to use HTML tags from within reST documents. Following
|
||||
is a usage example, which is in this case a contact form::
|
||||
|
||||
.. html::
|
||||
|
||||
<form method="GET" action="mailto:some email">
|
||||
<p>
|
||||
<input type="text" placeholder="Subject" name="subject">
|
||||
<br />
|
||||
<textarea name="body" placeholder="Message">
|
||||
</textarea>
|
||||
<br />
|
||||
<input type="reset"><input type="submit">
|
||||
</p>
|
||||
</form>
|
||||
|
||||
Related posts
|
||||
-------------
|
||||
|
||||
This plugin adds the ``related_posts`` variable to the article's context.
|
||||
To enable, add the following to your settings file::
|
||||
|
||||
from pelican.plugins import related_posts
|
||||
PLUGINS = [related_posts]
|
||||
|
||||
You can then use the ``article.related_posts`` variable in your templates.
|
||||
For example::
|
||||
|
||||
{% if article.related_posts %}
|
||||
<ul>
|
||||
{% for related_post in article.related_posts %}
|
||||
<li>{{ related_post }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
Sitemap
|
||||
-------
|
||||
|
||||
The sitemap plugin generates plain-text or XML sitemaps. You can use the
|
||||
``SITEMAP`` variable in your settings file to configure the behavior of the
|
||||
plugin.
|
||||
|
||||
The ``SITEMAP`` variable must be a Python dictionary, it can contain three keys:
|
||||
|
||||
- ``format``, which sets the output format of the plugin (``xml`` or ``txt``)
|
||||
|
||||
- ``priorities``, which is a dictionary with three keys:
|
||||
|
||||
- ``articles``, the priority for the URLs of the articles and their
|
||||
translations
|
||||
|
||||
- ``pages``, the priority for the URLs of the static pages
|
||||
|
||||
- ``indexes``, the priority for the URLs of the index pages, such as tags,
|
||||
author pages, categories indexes, archives, etc...
|
||||
|
||||
All the values of this dictionary must be decimal numbers between ``0`` and ``1``.
|
||||
|
||||
- ``changefreqs``, which is a dictionary with three items:
|
||||
|
||||
- ``articles``, the update frequency of the articles
|
||||
|
||||
- ``pages``, the update frequency of the pages
|
||||
|
||||
- ``indexes``, the update frequency of the index pages
|
||||
|
||||
Valid frequency values are ``always``, ``hourly``, ``daily``, ``weekly``, ``monthly``,
|
||||
``yearly`` and ``never``.
|
||||
|
||||
If a key is missing or a value is incorrect, it will be replaced with the
|
||||
default value.
|
||||
|
||||
The sitemap is saved in ``<output_path>/sitemap.<format>``.
|
||||
|
||||
.. note::
|
||||
``priorities`` and ``changefreqs`` are informations for search engines.
|
||||
They are only used in the XML sitemaps.
|
||||
For more information: <http://www.sitemaps.org/protocol.html#xmlTagDefinitions>
|
||||
|
||||
**Example**
|
||||
After Pelican 3.2, signal names were standardized. Older plugins
|
||||
may need to be updated to use the new names:
|
||||
|
||||
Here is an example configuration (it's also the default settings):
|
||||
========================== ===========================
|
||||
Old name New name
|
||||
========================== ===========================
|
||||
article_generate_context article_generator_context
|
||||
article_generate_finalized article_generator_finalized
|
||||
article_generate_preread article_generator_preread
|
||||
pages_generate_context page_generator_context
|
||||
pages_generate_preread page_generator_preread
|
||||
pages_generator_finalized page_generator_finalized
|
||||
pages_generator_init page_generator_init
|
||||
static_generate_context static_generator_context
|
||||
static_generate_preread static_generator_preread
|
||||
========================== ===========================
|
||||
|
||||
.. code-block:: python
|
||||
Recipes
|
||||
=======
|
||||
|
||||
PLUGINS=['pelican.plugins.sitemap',]
|
||||
We eventually realised some of the recipes to create plugins would be best
|
||||
shared in the documentation somewhere, so here they are!
|
||||
|
||||
SITEMAP = {
|
||||
'format': 'xml',
|
||||
'priorities': {
|
||||
'articles': 0.5,
|
||||
'indexes': 0.5,
|
||||
'pages': 0.5
|
||||
},
|
||||
'changefreqs': {
|
||||
'articles': 'monthly',
|
||||
'indexes': 'daily',
|
||||
'pages': 'monthly'
|
||||
}
|
||||
}
|
||||
How to create a new reader
|
||||
--------------------------
|
||||
|
||||
One thing you might want is to add support for your very own input format.
|
||||
While it might make sense to add this feature in Pelican core, we
|
||||
wisely chose to avoid this situation and instead have the different readers
|
||||
defined via plugins.
|
||||
|
||||
The rationale behind this choice is mainly that plugins are really easy to
|
||||
write and don't slow down Pelican itself when they're not active.
|
||||
|
||||
No more talking — here is an example::
|
||||
|
||||
from pelican import signals
|
||||
from pelican.readers import BaseReader
|
||||
|
||||
# Create a new reader class, inheriting from the pelican.reader.BaseReader
|
||||
class NewReader(BaseReader):
|
||||
enabled = True # Yeah, you probably want that :-)
|
||||
|
||||
# The list of file extensions you want this reader to match with.
|
||||
# If multiple readers were to use the same extension, the latest will
|
||||
# win (so the one you're defining here, most probably).
|
||||
file_extensions = ['yeah']
|
||||
|
||||
# You need to have a read method, which takes a filename and returns
|
||||
# some content and the associated metadata.
|
||||
def read(self, filename):
|
||||
metadata = {'title': 'Oh yeah',
|
||||
'category': 'Foo',
|
||||
'date': '2012-12-01'}
|
||||
|
||||
parsed = {}
|
||||
for key, value in metadata.items():
|
||||
parsed[key] = self.process_metadata(key, value)
|
||||
|
||||
return "Some content", parsed
|
||||
|
||||
def add_reader(readers):
|
||||
readers.reader_classes['yeah'] = NewReader
|
||||
|
||||
# This is how pelican works.
|
||||
def register():
|
||||
signals.readers_init.connect(add_reader)
|
||||
|
||||
|
||||
Adding a new generator
|
||||
----------------------
|
||||
|
||||
Adding a new generator is also really easy. You might want to have a look at
|
||||
:doc:`internals` for more information on how to create your own generator.
|
||||
|
||||
::
|
||||
|
||||
def get_generators(pelican_object):
|
||||
# define a new generator here if you need to
|
||||
return MyGenerator
|
||||
|
||||
signals.get_generators.connect(get_generators)
|
||||
|
|
|
|||
181
docs/publish.rst
Normal file
181
docs/publish.rst
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
Publish your site
|
||||
#################
|
||||
|
||||
Site generation
|
||||
===============
|
||||
|
||||
Once Pelican is installed and you have some content (e.g., in Markdown or reST
|
||||
format), you can convert your content into HTML via the ``pelican`` command,
|
||||
specifying the path to your content and (optionally) the path to your
|
||||
:doc:`settings<settings>` file::
|
||||
|
||||
pelican /path/to/your/content/ [-s path/to/your/settings.py]
|
||||
|
||||
The above command will generate your site and save it in the ``output/``
|
||||
folder, using the default theme to produce a simple site. The default theme
|
||||
consists of very simple HTML without styling and is provided so folks may use
|
||||
it as a basis for creating their own themes.
|
||||
|
||||
You can also tell Pelican to watch for your modifications, instead of
|
||||
manually re-running it every time you want to see your changes. To enable this,
|
||||
run the ``pelican`` command with the ``-r`` or ``--autoreload`` option.
|
||||
|
||||
Pelican has other command-line switches available. Have a look at the help to
|
||||
see all the options you can use::
|
||||
|
||||
pelican --help
|
||||
|
||||
Viewing the generated files
|
||||
---------------------------
|
||||
|
||||
The files generated by Pelican are static files, so you don't actually need
|
||||
anything special to view them. You can use your browser to open the generated
|
||||
HTML files directly::
|
||||
|
||||
firefox output/index.html
|
||||
|
||||
Because the above method may have trouble locating your CSS and other linked
|
||||
assets, running a simple web server using Python will often provide a more
|
||||
reliable previewing experience.
|
||||
|
||||
For Python 2, run::
|
||||
|
||||
cd output
|
||||
python -m SimpleHTTPServer
|
||||
|
||||
For Python 3, run::
|
||||
|
||||
cd output
|
||||
python -m http.server
|
||||
|
||||
Once the basic server has been started, you can preview your site at
|
||||
http://localhost:8000/
|
||||
|
||||
Deployment
|
||||
==========
|
||||
|
||||
After you have generated your site, previewed it in your local development
|
||||
environment, and are ready to deploy it to production, you might first
|
||||
re-generate your site with any production-specific settings (e.g., analytics
|
||||
feeds, etc.) that you may have defined::
|
||||
|
||||
pelican content -s publishconf.py
|
||||
|
||||
The steps for deploying your site will depend on where it will be hosted.
|
||||
If you have SSH access to a server running Nginx or Apache, you might use the
|
||||
``rsync`` tool to transmit your site files::
|
||||
|
||||
rsync --avc --delete output/ host.example.com:/var/www/your-site/
|
||||
|
||||
There are many other deployment options, some of which can be configured when
|
||||
first setting up your site via the ``pelican-quickstart`` command. See the
|
||||
:doc:`Tips<tips>` page for detail on publishing via GitHub Pages.
|
||||
|
||||
Automation
|
||||
==========
|
||||
|
||||
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
|
||||
``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
|
||||
should be customized to fit your particular needs and usage patterns. If you
|
||||
find one or both of these automation tools to be of limited utility, these
|
||||
files can deleted at any time and will not affect usage of the canonical
|
||||
``pelican`` command.
|
||||
|
||||
Following are automation tools that "wrap" the ``pelican`` command and can
|
||||
simplify the process of generating, previewing, and uploading your site.
|
||||
|
||||
Fabric
|
||||
------
|
||||
|
||||
The advantage of Fabric_ 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
|
||||
``sudo`` if your environment requires it::
|
||||
|
||||
pip install Fabric
|
||||
|
||||
Take a moment to open the ``fabfile.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
|
||||
|
||||
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
|
||||
|
||||
To serve the generated site so it can be previewed in your browser at
|
||||
http://localhost:8000/::
|
||||
|
||||
fab 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
|
||||
|
||||
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
|
||||
needs and preferences.
|
||||
|
||||
Make
|
||||
----
|
||||
|
||||
A ``Makefile`` is also automatically created for you when you say "yes" to
|
||||
the relevant question during the ``pelican-quickstart`` process. The advantage
|
||||
of this method is that the ``make`` command is built into most POSIX systems
|
||||
and thus doesn't require installing anything else in order to use it. The
|
||||
downside is that non-POSIX systems (e.g., Windows) do not include ``make``,
|
||||
and installing it on those systems can be a non-trivial task.
|
||||
|
||||
If you want to use ``make`` to generate your site, run::
|
||||
|
||||
make html
|
||||
|
||||
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::
|
||||
|
||||
make regenerate
|
||||
|
||||
To serve the generated site so it can be previewed in your browser at
|
||||
http://localhost:8000/::
|
||||
|
||||
make serve
|
||||
|
||||
Normally you would need to run ``make regenerate`` and ``make serve`` in two
|
||||
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
|
||||
|
||||
When you're ready to publish your site, you can upload it via the method(s) you
|
||||
chose during the ``pelican-quickstart`` questionnaire. For this example, we'll
|
||||
use rsync over ssh::
|
||||
|
||||
make rsync_upload
|
||||
|
||||
That's it! Your site should now be live.
|
||||
|
||||
(The default ``Makefile`` and ``devserver.sh`` scripts use the ``python`` and
|
||||
``pelican`` executables to complete its tasks. If you want to use different
|
||||
executables, such as ``python3``, you can set the ``PY`` and ``PELICAN``
|
||||
environment variables, respectively, to override the default executable names.)
|
||||
|
||||
.. _Fabric: http://fabfile.org/
|
||||
74
docs/quickstart.rst
Normal file
74
docs/quickstart.rst
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
Quickstart
|
||||
##########
|
||||
|
||||
Reading through all the documentation is highly recommended, but for the truly
|
||||
impatient, following are some quick steps to get started.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Install Pelican (and optionally Markdown if you intend to use it) on Python
|
||||
2.7.x or Python 3.3+ by running the following command in your preferred
|
||||
terminal, prefixing with ``sudo`` if permissions warrant::
|
||||
|
||||
pip install pelican markdown
|
||||
|
||||
Create a project
|
||||
----------------
|
||||
|
||||
First, choose a name for your project, create an appropriately-named directory
|
||||
for your site, and switch to that directory::
|
||||
|
||||
mkdir -p ~/projects/yoursite
|
||||
cd ~/projects/yoursite
|
||||
|
||||
Create a skeleton project via the ``pelican-quickstart`` command, which begins
|
||||
by asking some questions about your site::
|
||||
|
||||
pelican-quickstart
|
||||
|
||||
For questions that have default values denoted in brackets, feel free to use
|
||||
the Return key to accept those default values. When asked for your URL prefix,
|
||||
enter your domain name as indicated (e.g., ``http://example.com``).
|
||||
|
||||
Create an article
|
||||
-----------------
|
||||
|
||||
You cannot run Pelican until you have created some content. Use your preferred
|
||||
text editor to create your first article with the following content::
|
||||
|
||||
Title: My First Review
|
||||
Date: 2010-12-03 10:20
|
||||
Category: Review
|
||||
|
||||
Following is a review of my favorite mechanical keyboard.
|
||||
|
||||
Given that this example article is in Markdown format, save it as
|
||||
``~/projects/yoursite/content/keyboard-review.md``.
|
||||
|
||||
Generate your site
|
||||
------------------
|
||||
|
||||
From your project directory, run the ``pelican`` command to generate your site::
|
||||
|
||||
pelican content
|
||||
|
||||
Your site has now been generated inside the ``output`` directory. (You may see a
|
||||
warning related to feeds, but that is normal when developing locally and can be
|
||||
ignored for now.)
|
||||
|
||||
Preview your site
|
||||
-----------------
|
||||
|
||||
Open a new terminal session and run the following commands to switch to your
|
||||
``output`` directory and launch Python's built-in web server::
|
||||
|
||||
cd ~/projects/yoursite/output
|
||||
python -m SimpleHTTPServer
|
||||
|
||||
Preview your site by navigating to http://localhost:8000/ in your browser.
|
||||
|
||||
Continue reading the other documentation sections for more detail, and check out
|
||||
the Pelican wiki's Tutorials_ page for links to community-published tutorials.
|
||||
|
||||
.. _Tutorials: https://github.com/getpelican/pelican/wiki/Tutorials
|
||||
File diff suppressed because it is too large
Load diff
290
docs/themes.rst
290
docs/themes.rst
|
|
@ -1,13 +1,22 @@
|
|||
.. _theming-pelican:
|
||||
|
||||
How to create themes for Pelican
|
||||
################################
|
||||
Creating themes
|
||||
###############
|
||||
|
||||
Pelican uses the great `Jinja2 <http://jinja.pocoo.org/>`_ templating engine to
|
||||
generate its HTML output. Jinja2 syntax is really simple. If you want to
|
||||
create your own theme, feel free to take inspiration from the `"simple" theme
|
||||
To generate its HTML output, Pelican uses the `Jinja <http://jinja.pocoo.org/>`_
|
||||
templating engine due to its flexibility and straightforward syntax. If you want
|
||||
to create your own theme, feel free to take inspiration from the `"simple" theme
|
||||
<https://github.com/getpelican/pelican/tree/master/pelican/themes/simple/templates>`_.
|
||||
|
||||
To generate your site using a theme you have created (or downloaded manually and
|
||||
then modified), you can specify that theme via the ``-t`` flag::
|
||||
|
||||
pelican content -s pelicanconf.py -t /projects/your-site/themes/your-theme
|
||||
|
||||
If you'd rather not specify the theme on every invocation, you can define
|
||||
``THEME`` in your settings to point to the location of your preferred theme.
|
||||
|
||||
|
||||
Structure
|
||||
=========
|
||||
|
||||
|
|
@ -17,24 +26,26 @@ To make your own theme, you must follow the following structure::
|
|||
│ ├── css
|
||||
│ └── images
|
||||
└── templates
|
||||
├── archives.html // to display archives
|
||||
├── article.html // processed for each article
|
||||
├── author.html // processed for each author
|
||||
├── authors.html // must list all the authors
|
||||
├── categories.html // must list all the categories
|
||||
├── category.html // processed for each category
|
||||
├── index.html // the index. List all the articles
|
||||
├── page.html // processed for each page
|
||||
├── tag.html // processed for each tag
|
||||
└── tags.html // must list all the tags. Can be a tag cloud.
|
||||
├── archives.html // to display archives
|
||||
├── period_archives.html // to display time-period archives
|
||||
├── article.html // processed for each article
|
||||
├── author.html // processed for each author
|
||||
├── authors.html // must list all the authors
|
||||
├── categories.html // must list all the categories
|
||||
├── category.html // processed for each category
|
||||
├── index.html // the index (list all the articles)
|
||||
├── page.html // processed for each page
|
||||
├── tag.html // processed for each tag
|
||||
└── tags.html // must list all the tags. Can be a tag cloud.
|
||||
|
||||
* `static` contains all the static assets, which will be copied to the output
|
||||
`theme` folder. I've put the CSS and image folders here, but they are
|
||||
just examples. Put what you need here.
|
||||
`theme` folder. The above filesystem layout includes CSS and image folders,
|
||||
but those are just examples. Put what you need here.
|
||||
|
||||
* `templates` contains all the templates that will be used to generate the content.
|
||||
I've just put the mandatory templates here; you can define your own if it helps
|
||||
you keep things organized while creating your theme.
|
||||
The template files listed above are mandatory; you can add your own templates
|
||||
if it helps you keep things organized while creating your theme.
|
||||
|
||||
|
||||
Templates and variables
|
||||
=======================
|
||||
|
|
@ -43,8 +54,9 @@ The idea is to use a simple syntax that you can embed into your HTML pages.
|
|||
This document describes which templates should exist in a theme, and which
|
||||
variables will be passed to each template at generation time.
|
||||
|
||||
All templates will receive the variables defined in your settings file, if they
|
||||
are in all-caps. You can access them directly.
|
||||
All templates will receive the variables defined in your settings file, as long
|
||||
as they are in all-caps. You can access them directly.
|
||||
|
||||
|
||||
Common variables
|
||||
----------------
|
||||
|
|
@ -54,95 +66,160 @@ All of these settings will be available to all templates.
|
|||
============= ===================================================
|
||||
Variable Description
|
||||
============= ===================================================
|
||||
articles The list of articles, ordered descending by date
|
||||
output_file The name of the file currently being generated. For
|
||||
instance, when Pelican is rendering the home page,
|
||||
output_file will be "index.html".
|
||||
articles The list of articles, ordered descending by date.
|
||||
All the elements are `Article` objects, so you can
|
||||
access their attributes (e.g. title, summary, author
|
||||
etc.)
|
||||
etc.). Sometimes this is shadowed (for instance in
|
||||
the tags page). You will then find info about it
|
||||
in the `all_articles` variable.
|
||||
dates The same list of articles, but ordered by date,
|
||||
ascending
|
||||
tags A key-value dict containing the tags (the keys) and
|
||||
the list of respective articles (the values)
|
||||
categories A key-value dict containing the categories (keys)
|
||||
and the list of respective articles (values)
|
||||
ascending.
|
||||
tags A list of (tag, articles) tuples, containing all
|
||||
the tags.
|
||||
categories A list of (category, articles) tuples, containing
|
||||
all the categories and corresponding articles (values)
|
||||
pages The list of pages
|
||||
============= ===================================================
|
||||
|
||||
|
||||
Sorting
|
||||
-------
|
||||
|
||||
URL wrappers (currently categories, tags, and authors), have
|
||||
comparison methods that allow them to be easily sorted by name::
|
||||
|
||||
{% for tag, articles in tags|sort %}
|
||||
|
||||
If you want to sort based on different criteria, `Jinja's sort
|
||||
command`__ has a number of options.
|
||||
|
||||
__ http://jinja.pocoo.org/docs/templates/#sort
|
||||
|
||||
|
||||
Date Formatting
|
||||
---------------
|
||||
|
||||
Pelican formats the date according to your settings and locale
|
||||
(``DATE_FORMATS``/``DEFAULT_DATE_FORMAT``) and provides a
|
||||
``locale_date`` attribute. On the other hand, the ``date`` attribute will
|
||||
be a `datetime`_ object. If you need custom formatting for a date
|
||||
different than your settings, use the Jinja filter ``strftime``
|
||||
that comes with Pelican. Usage is same as Python `strftime`_ format,
|
||||
but the filter will do the right thing and format your date according
|
||||
to the locale given in your settings::
|
||||
|
||||
{{ article.date|strftime('%d %B %Y') }}
|
||||
|
||||
.. _datetime: http://docs.python.org/2/library/datetime.html#datetime-objects
|
||||
.. _strftime: http://docs.python.org/2/library/datetime.html#strftime-strptime-behavior
|
||||
|
||||
|
||||
index.html
|
||||
----------
|
||||
|
||||
This is the home page of your blog, generated at output/index.html.
|
||||
This is the home page or index of your blog, generated at ``index.html``.
|
||||
|
||||
If pagination is active, subsequent pages will reside in output/index`n`.html.
|
||||
If pagination is active, subsequent pages will reside in ``index{number}.html``.
|
||||
|
||||
=================== ===================================================
|
||||
====================== ===================================================
|
||||
Variable Description
|
||||
=================== ===================================================
|
||||
====================== ===================================================
|
||||
articles_paginator A paginator object for the list of articles
|
||||
articles_page The current page of articles
|
||||
articles_previous_page The previous page of articles (``None`` if page does
|
||||
not exist)
|
||||
articles_next_page The next page of articles (``None`` if page does
|
||||
not exist)
|
||||
dates_paginator A paginator object for the article list, ordered by
|
||||
date, ascending.
|
||||
dates_page The current page of articles, ordered by date,
|
||||
ascending.
|
||||
dates_previous_page The previous page of articles, ordered by date,
|
||||
ascending (``None`` if page does not exist)
|
||||
dates_next_page The next page of articles, ordered by date,
|
||||
ascending (``None`` if page does not exist)
|
||||
page_name 'index' -- useful for pagination links
|
||||
=================== ===================================================
|
||||
====================== ===================================================
|
||||
|
||||
|
||||
author.html
|
||||
-------------
|
||||
|
||||
This template will be processed for each of the existing authors, with
|
||||
output generated at output/author/`author_name`.html.
|
||||
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``.
|
||||
|
||||
If pagination is active, subsequent pages will reside at
|
||||
output/author/`author_name``n`.html.
|
||||
|
||||
=================== ===================================================
|
||||
====================== ===================================================
|
||||
Variable Description
|
||||
=================== ===================================================
|
||||
====================== ===================================================
|
||||
author The name of the author being processed
|
||||
articles Articles by this author
|
||||
dates Articles by this author, but ordered by date,
|
||||
ascending
|
||||
articles_paginator A paginator object for the list of articles
|
||||
articles_page The current page of articles
|
||||
articles_previous_page The previous page of articles (``None`` if page does
|
||||
not exist)
|
||||
articles_next_page The next page of articles (``None`` if page does
|
||||
not exist)
|
||||
dates_paginator A paginator object for the article list, ordered by
|
||||
date, ascending.
|
||||
dates_page The current page of articles, ordered by date,
|
||||
ascending.
|
||||
page_name 'author/`author_name`' -- useful for pagination
|
||||
links
|
||||
=================== ===================================================
|
||||
dates_previous_page The previous page of articles, ordered by date,
|
||||
ascending (``None`` if page does not exist)
|
||||
dates_next_page The next page of articles, ordered by date,
|
||||
ascending (``None`` if page does not exist)
|
||||
page_name AUTHOR_URL where everything after `{slug}` is
|
||||
removed -- useful for pagination links
|
||||
====================== ===================================================
|
||||
|
||||
|
||||
category.html
|
||||
-------------
|
||||
|
||||
This template will be processed for each of the existing categories, with
|
||||
output generated at output/category/`category_name`.html.
|
||||
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``.
|
||||
|
||||
If pagination is active, subsequent pages will reside at
|
||||
output/category/`category_name``n`.html.
|
||||
|
||||
=================== ===================================================
|
||||
====================== ===================================================
|
||||
Variable Description
|
||||
=================== ===================================================
|
||||
====================== ===================================================
|
||||
category The name of the category being processed
|
||||
articles Articles for this category
|
||||
dates Articles for this category, but ordered by date,
|
||||
ascending
|
||||
articles_paginator A paginator object for the list of articles
|
||||
articles_page The current page of articles
|
||||
articles_previous_page The previous page of articles (``None`` if page does
|
||||
not exist)
|
||||
articles_next_page The next page of articles (``None`` if page does
|
||||
not exist)
|
||||
dates_paginator A paginator object for the list of articles,
|
||||
ordered by date, ascending
|
||||
dates_page The current page of articles, ordered by date,
|
||||
ascending
|
||||
page_name 'category/`category_name`' -- useful for pagination
|
||||
links
|
||||
=================== ===================================================
|
||||
dates_previous_page The previous page of articles, ordered by date,
|
||||
ascending (``None`` if page does not exist)
|
||||
dates_next_page The next page of articles, ordered by date,
|
||||
ascending (``None`` if page does not exist)
|
||||
page_name CATEGORY_URL where everything after `{slug}` is
|
||||
removed -- useful for pagination links
|
||||
====================== ===================================================
|
||||
|
||||
|
||||
article.html
|
||||
-------------
|
||||
|
||||
This template will be processed for each article, with .html files saved
|
||||
as output/`article_name`.html. Here are the specific variables it gets.
|
||||
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
|
||||
rendering.
|
||||
|
||||
============= ===================================================
|
||||
Variable Description
|
||||
|
|
@ -151,11 +228,39 @@ article The article object to be displayed
|
|||
category The name of the category for the current article
|
||||
============= ===================================================
|
||||
|
||||
Any metadata that you put in the header of the article source file
|
||||
will be available as fields on the ``article`` object. The field name will be
|
||||
the same as the name of the metadata field, except in all-lowercase characters.
|
||||
|
||||
For example, you could add a field called `FacebookImage` to your article
|
||||
metadata, as shown below:
|
||||
|
||||
.. code-block:: markdown
|
||||
|
||||
Title: I love Python more than music
|
||||
Date: 2013-11-06 10:06
|
||||
Tags: personal, python
|
||||
Category: Tech
|
||||
Slug: python-je-l-aime-a-mourir
|
||||
Author: Francis Cabrel
|
||||
FacebookImage: http://franciscabrel.com/images/pythonlove.png
|
||||
|
||||
This new metadata will be made available as `article.facebookimage` in your
|
||||
`article.html` template. This would allow you, for example, to specify an
|
||||
image for the Facebook open graph tags that will change for each article:
|
||||
|
||||
.. code-block:: html+jinja
|
||||
|
||||
<meta property="og:image" content="{{ article.facebookimage }}"/>
|
||||
|
||||
|
||||
page.html
|
||||
---------
|
||||
|
||||
This template will be processed for each page, with corresponding .html files
|
||||
saved as output/`page_name`.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
|
||||
rendering.
|
||||
|
||||
============= ===================================================
|
||||
Variable Description
|
||||
|
|
@ -164,30 +269,64 @@ page The page object to be displayed. You can access its
|
|||
title, slug, and content.
|
||||
============= ===================================================
|
||||
|
||||
|
||||
tag.html
|
||||
--------
|
||||
|
||||
This template will be processed for each tag, with corresponding .html files
|
||||
saved as output/tag/`tag_name`.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``.
|
||||
|
||||
If pagination is active, subsequent pages will reside at
|
||||
output/tag/`tag_name``n`.html.
|
||||
|
||||
=================== ===================================================
|
||||
====================== ===================================================
|
||||
Variable Description
|
||||
=================== ===================================================
|
||||
====================== ===================================================
|
||||
tag The name of the tag being processed
|
||||
articles Articles related to this tag
|
||||
dates Articles related to this tag, but ordered by date,
|
||||
ascending
|
||||
articles_paginator A paginator object for the list of articles
|
||||
articles_page The current page of articles
|
||||
articles_previous_page The previous page of articles (``None`` if page does
|
||||
not exist)
|
||||
articles_next_page The next page of articles (``None`` if page does
|
||||
not exist)
|
||||
dates_paginator A paginator object for the list of articles,
|
||||
ordered by date, ascending
|
||||
dates_page The current page of articles, ordered by date,
|
||||
ascending
|
||||
page_name 'tag/`tag_name`' -- useful for pagination links
|
||||
dates_previous_page The previous page of articles, ordered by date,
|
||||
ascending (``None`` if page does not exist)
|
||||
dates_next_page The next page of articles, ordered by date,
|
||||
ascending (``None`` if page does not exist)
|
||||
page_name TAG_URL where everything after `{slug}` is removed
|
||||
-- useful for pagination links
|
||||
====================== ===================================================
|
||||
|
||||
|
||||
period_archives.html
|
||||
--------------------
|
||||
|
||||
This template will be processed for each year of your posts if a path
|
||||
for ``YEAR_ARCHIVE_SAVE_AS`` is defined, each month if ``MONTH_ARCHIVE_SAVE_AS``
|
||||
is defined, and each day if ``DAY_ARCHIVE_SAVE_AS`` is defined.
|
||||
|
||||
=================== ===================================================
|
||||
Variable Description
|
||||
=================== ===================================================
|
||||
period A tuple of the form (`year`, `month`, `day`) that
|
||||
indicates the current time period. `year` and `day`
|
||||
are numbers while `month` is a string. This tuple
|
||||
only contains `year` if the time period is a
|
||||
given year. It contains both `year` and `month`
|
||||
if the time period is over years and months and
|
||||
so on.
|
||||
|
||||
=================== ===================================================
|
||||
|
||||
You can see an example of how to use `period` in the `"simple" theme
|
||||
<https://github.com/getpelican/pelican/blob/master/pelican/themes/simple/templates/period_archives.html>`_.
|
||||
|
||||
|
||||
Feeds
|
||||
=====
|
||||
|
|
@ -198,11 +337,14 @@ Here is a complete list of the feed variables::
|
|||
|
||||
FEED_ATOM
|
||||
FEED_RSS
|
||||
FEED_ALL_ATOM
|
||||
FEED_ALL_RSS
|
||||
CATEGORY_FEED_ATOM
|
||||
CATEGORY_FEED_RSS
|
||||
TAG_FEED_ATOM
|
||||
TAG_FEED_RSS
|
||||
TRANSLATION_FEED
|
||||
TRANSLATION_FEED_ATOM
|
||||
TRANSLATION_FEED_RSS
|
||||
|
||||
|
||||
Inheritance
|
||||
|
|
@ -216,7 +358,8 @@ missing, it will be replaced by the matching template from the ``simple`` theme.
|
|||
So if the HTML structure of a template in the ``simple`` theme is right for you,
|
||||
you don't have to write a new template from scratch.
|
||||
|
||||
You can also extend templates from the ``simple`` themes in your own themes by using the ``{% extends %}`` directive as in the following example:
|
||||
You can also extend templates from the ``simple`` theme in your own themes by
|
||||
using the ``{% extends %}`` directive as in the following example:
|
||||
|
||||
.. code-block:: html+jinja
|
||||
|
||||
|
|
@ -244,14 +387,17 @@ The first file is the ``templates/base.html`` template:
|
|||
<link rel="stylesheet" type="text/css" href="{{ SITEURL }}/theme/css/style.css" />
|
||||
{% endblock %}
|
||||
|
||||
1. On the first line, we extend the ``base.html`` template from the ``simple``
|
||||
theme, so we don't have to rewrite the entire file.
|
||||
2. On the third line, we open the ``head`` block which has already been defined
|
||||
in the ``simple`` theme.
|
||||
3. On the fourth line, the function ``super()`` keeps the content previously
|
||||
inserted in the ``head`` block.
|
||||
4. On the fifth line, we append a stylesheet to the page.
|
||||
5. On the last line, we close the ``head`` block.
|
||||
|
||||
1. On the first line, we extend the ``base.html`` template from the ``simple`` theme, so we don't have to rewrite the entire file.
|
||||
2. On the third line, we open the ``head`` block which has already been defined in the ``simple`` theme.
|
||||
3. On the fourth line, the function ``super()`` keeps the content previously inserted in the ``head`` block.
|
||||
4. On the fifth line, we append a stylesheet to the page.
|
||||
5. On the last line, we close the ``head`` block.
|
||||
|
||||
This file will be extended by all the other templates, so the stylesheet will be linked from all pages.
|
||||
This file will be extended by all the other templates, so the stylesheet will
|
||||
be linked from all pages.
|
||||
|
||||
style.css
|
||||
"""""""""
|
||||
|
|
|
|||
113
docs/tips.rst
113
docs/tips.rst
|
|
@ -6,47 +6,96 @@ Here are some tips about Pelican that you might find useful.
|
|||
Publishing to GitHub
|
||||
====================
|
||||
|
||||
GitHub comes with an interesting "pages" feature: you can upload things there
|
||||
and it will be available directly from their servers. As Pelican is a static
|
||||
file generator, we can take advantage of this.
|
||||
|
||||
User Pages
|
||||
----------
|
||||
GitHub allows you to create user pages in the form of ``username.github.com``.
|
||||
Whatever is created in the master branch will be published. For this purpose,
|
||||
just the output generated by Pelican needs to pushed to GitHub.
|
||||
|
||||
So given a repository containing your articles, just run Pelican over the posts
|
||||
and deploy the master branch to GitHub::
|
||||
|
||||
$ pelican -s pelican.conf.py ./path/to/posts -o /path/to/output
|
||||
|
||||
Now add all the files in the output directory generated by Pelican::
|
||||
|
||||
$ git add /path/to/output/*
|
||||
$ git commit -am "Your Message"
|
||||
$ git push origin master
|
||||
`GitHub Pages <https://help.github.com/categories/20/articles>`_ offer an easy
|
||||
and convenient way to publish Pelican sites. There are `two types of GitHub
|
||||
Pages <https://help.github.com/articles/user-organization-and-project-pages>`_:
|
||||
*Project Pages* and *User Pages*. Pelican sites can be published as both
|
||||
Project Pages and User Pages.
|
||||
|
||||
Project Pages
|
||||
-------------
|
||||
For creating Project pages, a branch called ``gh-pages`` is used for publishing.
|
||||
The excellent `ghp-import <https://github.com/davisp/ghp-import>`_ makes this
|
||||
really easy, which can be installed via::
|
||||
|
||||
$ pip install ghp-import
|
||||
To publish a Pelican site as a Project Page you need to *push* the content of
|
||||
the ``output`` dir generated by Pelican to a repository's ``gh-pages`` branch
|
||||
on GitHub.
|
||||
|
||||
Then, given a repository containing your articles, you would simply run
|
||||
Pelican and upload the output to GitHub::
|
||||
The excellent `ghp-import <https://github.com/davisp/ghp-import>`_, which can
|
||||
be installed with ``easy_install`` or ``pip``, makes this process really easy.
|
||||
|
||||
$ pelican -s pelican.conf.py .
|
||||
For example, if the source of your Pelican site is contained in a GitHub
|
||||
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
|
||||
$ git push origin gh-pages
|
||||
|
||||
And that's it.
|
||||
The ``ghp-import output`` command updates the local ``gh-pages`` branch with
|
||||
the content of the ``output`` directory (creating the branch if it doesn't
|
||||
already exist). The ``git push origin gh-pages`` command updates the remote
|
||||
``gh-pages`` branch, effectively publishing the Pelican site.
|
||||
|
||||
If you want, you can put that directly into a post-commit hook, so each time you
|
||||
commit, your blog is up-to-date on GitHub!
|
||||
.. note::
|
||||
|
||||
Put the following into ``.git/hooks/post-commit``::
|
||||
The ``github`` target of the Makefile created by the ``pelican-quickstart``
|
||||
command publishes the Pelican site as Project Pages, as described above.
|
||||
|
||||
pelican -s pelican.conf.py . && ghp-import output && git push origin gh-pages
|
||||
User Pages
|
||||
----------
|
||||
|
||||
To publish a Pelican site in the form of User Pages, you need to *push* the
|
||||
content of the ``output`` dir generated by Pelican to the ``master`` branch of
|
||||
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
|
||||
$ 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
|
||||
by the ``ghp-import`` command) to the ``elemoine.github.io`` repository's
|
||||
``master`` branch on GitHub.
|
||||
|
||||
.. note::
|
||||
|
||||
To publish your Pelican site as User Pages, feel free to adjust the
|
||||
``github`` target of the Makefile.
|
||||
|
||||
Extra Tips
|
||||
----------
|
||||
|
||||
Tip #1:
|
||||
|
||||
To automatically update your Pelican site on each commit, you can create
|
||||
a post-commit hook. For example, you can add the following to
|
||||
``.git/hooks/post-commit``::
|
||||
|
||||
pelican content -o output -s pelicanconf.py && ghp-import output && git push origin gh-pages
|
||||
|
||||
Tip #2:
|
||||
|
||||
To use a `custom domain
|
||||
<https://help.github.com/articles/setting-up-a-custom-domain-with-pages>`_ with
|
||||
GitHub Pages, you need to put the domain of your site (e.g.,
|
||||
``blog.example.com``) inside a ``CNAME`` file at the root of your site. To do
|
||||
this, create the ``content/extra/`` directory and add a ``CNAME`` file to it.
|
||||
Then use the ``STATIC_PATHS`` setting to tell Pelican to copy this file to your
|
||||
output directory. For example::
|
||||
|
||||
STATIC_PATHS = ['images', 'extra/CNAME']
|
||||
EXTRA_PATH_METADATA = {'extra/CNAME': {'path': 'CNAME'},}
|
||||
|
||||
How to add YouTube or Vimeo Videos
|
||||
==================================
|
||||
|
||||
The easiest way is to paste the embed code of the video from these sites
|
||||
directly into your source content.
|
||||
|
||||
Alternatively, you can also use Pelican plugins like ``liquid_tags``,
|
||||
``pelican_youtube``, or ``pelican_vimeo`` to embed videos in your content.
|
||||
|
||||
Moreover, markup languages like reST and Markdown have plugins that let you
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -1,94 +1,96 @@
|
|||
import copy
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals, print_function
|
||||
import six
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import argparse
|
||||
import locale
|
||||
import collections
|
||||
|
||||
# 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 import signals
|
||||
|
||||
from pelican.generators import (ArticlesGenerator, PagesGenerator,
|
||||
StaticGenerator, PdfGenerator,
|
||||
LessCSSGenerator, SourceFileGenerator)
|
||||
from pelican.log import init
|
||||
from pelican.settings import read_settings, _DEFAULT_CONFIG
|
||||
from pelican.utils import (clean_output_dir, files_changed, file_changed,
|
||||
NoFilesError)
|
||||
StaticGenerator, SourceFileGenerator,
|
||||
TemplatePagesGenerator)
|
||||
from pelican.readers import Readers
|
||||
from pelican.settings import read_settings
|
||||
from pelican.utils import clean_output_dir, folder_watcher, file_watcher
|
||||
from pelican.writers import Writer
|
||||
|
||||
__major__ = 3
|
||||
__minor__ = 0
|
||||
__version__ = "{0}.{1}".format(__major__, __minor__)
|
||||
__version__ = "3.5.0"
|
||||
|
||||
DEFAULT_CONFIG_NAME = 'pelicanconf.py'
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Pelican(object):
|
||||
def __init__(self, settings=None, path=None, theme=None, output_path=None,
|
||||
markup=None, delete_outputdir=False, plugin_path=None):
|
||||
"""Read the settings, and performs some checks on the environment
|
||||
before doing anything else.
|
||||
|
||||
def __init__(self, settings):
|
||||
"""
|
||||
Pelican initialisation, performs some checks on the environment before
|
||||
doing anything else.
|
||||
"""
|
||||
if settings is None:
|
||||
settings = copy.deepcopy(_DEFAULT_CONFIG)
|
||||
|
||||
self.path = path or settings['PATH']
|
||||
if not self.path:
|
||||
raise Exception('You need to specify a path containing the content'
|
||||
' (see pelican --help for more information)')
|
||||
|
||||
if self.path.endswith('/'):
|
||||
self.path = self.path[:-1]
|
||||
|
||||
# define the default settings
|
||||
self.settings = settings
|
||||
|
||||
self._handle_deprecation()
|
||||
|
||||
self.theme = theme or settings['THEME']
|
||||
output_path = output_path or settings['OUTPUT_PATH']
|
||||
self.output_path = os.path.realpath(output_path)
|
||||
self.markup = markup or settings['MARKUP']
|
||||
self.delete_outputdir = delete_outputdir \
|
||||
or settings['DELETE_OUTPUT_DIRECTORY']
|
||||
|
||||
# find the theme in pelican.theme if the given one does not exists
|
||||
if not os.path.exists(self.theme):
|
||||
theme_path = os.sep.join([os.path.dirname(
|
||||
os.path.abspath(__file__)), "themes/%s" % self.theme])
|
||||
if os.path.exists(theme_path):
|
||||
self.theme = theme_path
|
||||
else:
|
||||
raise Exception("Impossible to find the theme %s" % theme)
|
||||
self.path = settings['PATH']
|
||||
self.theme = settings['THEME']
|
||||
self.output_path = settings['OUTPUT_PATH']
|
||||
self.ignore_files = settings['IGNORE_FILES']
|
||||
self.delete_outputdir = settings['DELETE_OUTPUT_DIRECTORY']
|
||||
self.output_retention = settings['OUTPUT_RETENTION']
|
||||
|
||||
self.init_path()
|
||||
self.init_plugins()
|
||||
signals.initialized.send(self)
|
||||
|
||||
def init_path(self):
|
||||
if not any(p in sys.path for p in ['', '.']):
|
||||
if not any(p in sys.path for p in ['', os.curdir]):
|
||||
logger.debug("Adding current directory to system path")
|
||||
sys.path.insert(0, '')
|
||||
|
||||
def init_plugins(self):
|
||||
self.plugins = self.settings['PLUGINS']
|
||||
for plugin in self.plugins:
|
||||
self.plugins = []
|
||||
logger.debug('Temporarily adding PLUGIN_PATHS to system path')
|
||||
_sys_path = sys.path[:]
|
||||
for pluginpath in self.settings['PLUGIN_PATHS']:
|
||||
sys.path.insert(0, pluginpath)
|
||||
for plugin in self.settings['PLUGINS']:
|
||||
# if it's a string, then import it
|
||||
if isinstance(plugin, basestring):
|
||||
logger.debug("Loading plugin `{0}' ...".format(plugin))
|
||||
plugin = __import__(plugin, globals(), locals(), 'module')
|
||||
if isinstance(plugin, six.string_types):
|
||||
logger.debug("Loading plugin `%s`", plugin)
|
||||
try:
|
||||
plugin = __import__(plugin, globals(), locals(),
|
||||
str('module'))
|
||||
except ImportError as e:
|
||||
logger.error(
|
||||
"Cannot load plugin `%s`\n%s", plugin, e)
|
||||
continue
|
||||
|
||||
logger.debug("Registering plugin `{0}'".format(plugin.__name__))
|
||||
logger.debug("Registering plugin `%s`", plugin.__name__)
|
||||
plugin.register()
|
||||
self.plugins.append(plugin)
|
||||
logger.debug('Restoring system path')
|
||||
sys.path = _sys_path
|
||||
|
||||
def _handle_deprecation(self):
|
||||
|
||||
if self.settings.get('CLEAN_URLS', False):
|
||||
logger.warning('Found deprecated `CLEAN_URLS` in settings.'
|
||||
' Modifying the following settings for the'
|
||||
' same behaviour.')
|
||||
' Modifying the following settings for the'
|
||||
' same behaviour.')
|
||||
|
||||
self.settings['ARTICLE_URL'] = '{slug}/'
|
||||
self.settings['ARTICLE_LANG_URL'] = '{slug}-{lang}/'
|
||||
|
|
@ -97,12 +99,12 @@ class Pelican(object):
|
|||
|
||||
for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL',
|
||||
'PAGE_LANG_URL'):
|
||||
logger.warning("%s = '%s'" % (setting, self.settings[setting]))
|
||||
logger.warning("%s = '%s'", setting, self.settings[setting])
|
||||
|
||||
if self.settings.get('ARTICLE_PERMALINK_STRUCTURE', False):
|
||||
logger.warning('Found deprecated `ARTICLE_PERMALINK_STRUCTURE` in'
|
||||
' settings. Modifying the following settings for'
|
||||
' the same behaviour.')
|
||||
' settings. Modifying the following settings for'
|
||||
' the same behaviour.')
|
||||
|
||||
structure = self.settings['ARTICLE_PERMALINK_STRUCTURE']
|
||||
|
||||
|
|
@ -116,175 +118,259 @@ class Pelican(object):
|
|||
structure = re.sub('^/', '', structure)
|
||||
|
||||
for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL',
|
||||
'PAGE_LANG_URL', 'ARTICLE_SAVE_AS',
|
||||
'ARTICLE_LANG_SAVE_AS', 'PAGE_SAVE_AS',
|
||||
'PAGE_LANG_SAVE_AS'):
|
||||
'PAGE_LANG_URL', 'DRAFT_URL', 'DRAFT_LANG_URL',
|
||||
'ARTICLE_SAVE_AS', 'ARTICLE_LANG_SAVE_AS',
|
||||
'DRAFT_SAVE_AS', 'DRAFT_LANG_SAVE_AS',
|
||||
'PAGE_SAVE_AS', 'PAGE_LANG_SAVE_AS'):
|
||||
self.settings[setting] = os.path.join(structure,
|
||||
self.settings[setting])
|
||||
logger.warning("%s = '%s'" % (setting, self.settings[setting]))
|
||||
logger.warning("%s = '%s'", setting, self.settings[setting])
|
||||
|
||||
if self.settings.get('FEED', False):
|
||||
logger.warning('Found deprecated `FEED` in settings. Modify FEED'
|
||||
' to FEED_ATOM in your settings and theme for the same behavior.'
|
||||
' Temporarily setting FEED_ATOM for backwards compatibility.')
|
||||
self.settings['FEED_ATOM'] = self.settings['FEED']
|
||||
|
||||
if self.settings.get('TAG_FEED', False):
|
||||
logger.warning('Found deprecated `TAG_FEED` in settings. Modify '
|
||||
' TAG_FEED to TAG_FEED_ATOM in your settings and theme for the '
|
||||
'same behavior. Temporarily setting TAG_FEED_ATOM for backwards '
|
||||
'compatibility.')
|
||||
self.settings['TAG_FEED_ATOM'] = self.settings['TAG_FEED']
|
||||
|
||||
if self.settings.get('CATEGORY_FEED', False):
|
||||
logger.warning('Found deprecated `CATEGORY_FEED` in settings. '
|
||||
'Modify CATEGORY_FEED to CATEGORY_FEED_ATOM in your settings and '
|
||||
'theme for the same behavior. Temporarily setting '
|
||||
'CATEGORY_FEED_ATOM for backwards compatibility.')
|
||||
self.settings['CATEGORY_FEED_ATOM'] =\
|
||||
self.settings['CATEGORY_FEED']
|
||||
for new, old in [('FEED', 'FEED_ATOM'), ('TAG_FEED', 'TAG_FEED_ATOM'),
|
||||
('CATEGORY_FEED', 'CATEGORY_FEED_ATOM'),
|
||||
('TRANSLATION_FEED', 'TRANSLATION_FEED_ATOM')]:
|
||||
if self.settings.get(new, False):
|
||||
logger.warning(
|
||||
'Found deprecated `%(new)s` in settings. Modify %(new)s '
|
||||
'to %(old)s in your settings and theme for the same '
|
||||
'behavior. Temporarily setting %(old)s for backwards '
|
||||
'compatibility.',
|
||||
{'new': new, 'old': old}
|
||||
)
|
||||
self.settings[old] = self.settings[new]
|
||||
|
||||
def run(self):
|
||||
"""Run the generators and return"""
|
||||
start_time = time.time()
|
||||
|
||||
context = self.settings.copy()
|
||||
# Share these among all the generators and content objects:
|
||||
context['filenames'] = {} # maps source path to Content object or None
|
||||
context['localsiteurl'] = self.settings['SITEURL']
|
||||
|
||||
generators = [
|
||||
cls(
|
||||
context,
|
||||
self.settings,
|
||||
self.path,
|
||||
self.theme,
|
||||
self.output_path,
|
||||
self.markup,
|
||||
self.delete_outputdir
|
||||
context=context,
|
||||
settings=self.settings,
|
||||
path=self.path,
|
||||
theme=self.theme,
|
||||
output_path=self.output_path,
|
||||
) for cls in self.get_generator_classes()
|
||||
]
|
||||
|
||||
# erase the directory if it is not the source and if that's
|
||||
# explicitly asked
|
||||
if (self.delete_outputdir and not
|
||||
os.path.realpath(self.path).startswith(self.output_path)):
|
||||
clean_output_dir(self.output_path, self.output_retention)
|
||||
|
||||
for p in generators:
|
||||
if hasattr(p, 'generate_context'):
|
||||
p.generate_context()
|
||||
|
||||
# erase the directory if it is not the source and if that's
|
||||
# explicitely asked
|
||||
if (self.delete_outputdir and not
|
||||
os.path.realpath(self.path).startswith(self.output_path)):
|
||||
clean_output_dir(self.output_path)
|
||||
|
||||
writer = self.get_writer()
|
||||
|
||||
# pass the assets environment to the generators
|
||||
if self.settings['WEBASSETS']:
|
||||
generators[1].env.assets_environment = generators[0].assets_env
|
||||
generators[2].env.assets_environment = generators[0].assets_env
|
||||
|
||||
for p in generators:
|
||||
if hasattr(p, 'generate_output'):
|
||||
p.generate_output(writer)
|
||||
|
||||
signals.finalized.send(self)
|
||||
|
||||
articles_generator = next(g for g in generators
|
||||
if isinstance(g, ArticlesGenerator))
|
||||
pages_generator = next(g for g in generators
|
||||
if isinstance(g, PagesGenerator))
|
||||
|
||||
print('Done: Processed {} article(s), {} draft(s) and {} page(s) in ' \
|
||||
'{:.2f} seconds.'.format(
|
||||
len(articles_generator.articles) + len(articles_generator.translations),
|
||||
len(articles_generator.drafts) + \
|
||||
len(articles_generator.drafts_translations),
|
||||
len(pages_generator.pages) + len(pages_generator.translations),
|
||||
time.time() - start_time))
|
||||
|
||||
def get_generator_classes(self):
|
||||
generators = [StaticGenerator, ArticlesGenerator, PagesGenerator]
|
||||
if self.settings['PDF_GENERATOR']:
|
||||
generators.append(PdfGenerator)
|
||||
if self.settings['LESS_GENERATOR']: # can be True or PATH to lessc
|
||||
generators.append(LessCSSGenerator)
|
||||
generators = [ArticlesGenerator, PagesGenerator]
|
||||
|
||||
if self.settings['TEMPLATE_PAGES']:
|
||||
generators.append(TemplatePagesGenerator)
|
||||
if self.settings['OUTPUT_SOURCES']:
|
||||
generators.append(SourceFileGenerator)
|
||||
|
||||
for pair in signals.get_generators.send(self):
|
||||
(funct, value) = pair
|
||||
|
||||
if not isinstance(value, (tuple, list)):
|
||||
if not isinstance(value, collections.Iterable):
|
||||
value = (value, )
|
||||
|
||||
for v in value:
|
||||
if isinstance(v, type):
|
||||
logger.debug('Found generator: {0}'.format(v))
|
||||
logger.debug('Found generator: %s', v)
|
||||
generators.append(v)
|
||||
|
||||
# StaticGenerator must run last, so it can identify files that
|
||||
# were skipped by the other generators, and so static files can
|
||||
# have their output paths overridden by the {attach} link syntax.
|
||||
generators.append(StaticGenerator)
|
||||
return generators
|
||||
|
||||
def get_writer(self):
|
||||
return Writer(self.output_path, settings=self.settings)
|
||||
writers = [ w for (_, w) in signals.get_writer.send(self)
|
||||
if isinstance(w, type) ]
|
||||
writers_found = len(writers)
|
||||
if writers_found == 0:
|
||||
return Writer(self.output_path, settings=self.settings)
|
||||
else:
|
||||
writer = writers[0]
|
||||
if writers_found == 1:
|
||||
logger.debug('Found writer: %s', writer)
|
||||
else:
|
||||
logger.warning(
|
||||
'%s writers found, using only first one: %s',
|
||||
writers_found, writer)
|
||||
return writer(self.output_path, settings=self.settings)
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
parser = argparse.ArgumentParser(description="""A tool to generate a
|
||||
static blog, with restructured text input files.""",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser = argparse.ArgumentParser(
|
||||
description="""A tool to generate a static blog,
|
||||
with restructured text input files.""",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||
)
|
||||
|
||||
parser.add_argument(dest='path', nargs='?',
|
||||
help='Path where to find the content files.',
|
||||
default=None)
|
||||
help='Path where to find the content files.',
|
||||
default=None)
|
||||
|
||||
parser.add_argument('-t', '--theme-path', dest='theme',
|
||||
help='Path where to find the theme templates. If not specified, it'
|
||||
'will use the default one included with pelican.')
|
||||
help='Path where to find the theme templates. If not '
|
||||
'specified, it will use the default one included with '
|
||||
'pelican.')
|
||||
|
||||
parser.add_argument('-o', '--output', dest='output',
|
||||
help='Where to output the generated files. If not specified, a '
|
||||
'directory will be created, named "output" in the current path.')
|
||||
|
||||
parser.add_argument('-m', '--markup', dest='markup',
|
||||
help='The list of markup language to use (rst or md). Please indicate '
|
||||
'them separated by commas.')
|
||||
help='Where to output the generated files. If not '
|
||||
'specified, a directory will be created, named '
|
||||
'"output" in the current path.')
|
||||
|
||||
parser.add_argument('-s', '--settings', dest='settings',
|
||||
help='The settings of the application.')
|
||||
help='The settings of the application, this is '
|
||||
'automatically set to {0} if a file exists with this '
|
||||
'name.'.format(DEFAULT_CONFIG_NAME))
|
||||
|
||||
parser.add_argument('-d', '--delete-output-directory',
|
||||
dest='delete_outputdir',
|
||||
action='store_true', help='Delete the output directory.')
|
||||
dest='delete_outputdir', action='store_true',
|
||||
default=None, help='Delete the output directory.')
|
||||
|
||||
parser.add_argument('-v', '--verbose', action='store_const',
|
||||
const=logging.INFO, dest='verbosity',
|
||||
help='Show all messages.')
|
||||
const=logging.INFO, dest='verbosity',
|
||||
help='Show all messages.')
|
||||
|
||||
parser.add_argument('-q', '--quiet', action='store_const',
|
||||
const=logging.CRITICAL, dest='verbosity',
|
||||
help='Show only critical errors.')
|
||||
const=logging.CRITICAL, dest='verbosity',
|
||||
help='Show only critical errors.')
|
||||
|
||||
parser.add_argument('-D', '--debug', action='store_const',
|
||||
const=logging.DEBUG, dest='verbosity',
|
||||
help='Show all message, including debug messages.')
|
||||
const=logging.DEBUG, dest='verbosity',
|
||||
help='Show all messages, including debug messages.')
|
||||
|
||||
parser.add_argument('--version', action='version', version=__version__,
|
||||
help='Print the pelican version and exit.')
|
||||
help='Print the pelican version and exit.')
|
||||
|
||||
parser.add_argument('-r', '--autoreload', dest='autoreload',
|
||||
action='store_true',
|
||||
help="Relaunch pelican each time a modification occurs"
|
||||
" on the content files.")
|
||||
action='store_true',
|
||||
help='Relaunch pelican each time a modification occurs'
|
||||
' on the content files.')
|
||||
|
||||
parser.add_argument('--cache-path', dest='cache_path',
|
||||
help=('Directory in which to store cache files. '
|
||||
'If not specified, defaults to "cache".'))
|
||||
|
||||
parser.add_argument('--ignore-cache', action='store_true',
|
||||
dest='ignore_cache', help='Ignore content cache '
|
||||
'from previous runs by not loading cache files.')
|
||||
|
||||
parser.add_argument('-w', '--write-selected', type=str,
|
||||
dest='selected_paths', default=None,
|
||||
help='Comma separated list of selected paths to write')
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def get_config(args):
|
||||
config = {}
|
||||
if args.path:
|
||||
config['PATH'] = os.path.abspath(os.path.expanduser(args.path))
|
||||
if args.output:
|
||||
config['OUTPUT_PATH'] = \
|
||||
os.path.abspath(os.path.expanduser(args.output))
|
||||
if args.theme:
|
||||
abstheme = os.path.abspath(os.path.expanduser(args.theme))
|
||||
config['THEME'] = abstheme if os.path.exists(abstheme) else args.theme
|
||||
if args.delete_outputdir is not None:
|
||||
config['DELETE_OUTPUT_DIRECTORY'] = args.delete_outputdir
|
||||
if args.ignore_cache:
|
||||
config['LOAD_CONTENT_CACHE'] = False
|
||||
if args.cache_path:
|
||||
config['CACHE_PATH'] = args.cache_path
|
||||
if args.selected_paths:
|
||||
config['WRITE_SELECTED'] = args.selected_paths.split(',')
|
||||
config['DEBUG'] = args.verbosity == logging.DEBUG
|
||||
|
||||
# argparse returns bytes in Py2. There is no definite answer as to which
|
||||
# encoding argparse (or sys.argv) uses.
|
||||
# "Best" option seems to be locale.getpreferredencoding()
|
||||
# ref: http://mail.python.org/pipermail/python-list/2006-October/405766.html
|
||||
if not six.PY3:
|
||||
enc = locale.getpreferredencoding()
|
||||
for key in config:
|
||||
if key in ('PATH', 'OUTPUT_PATH', 'THEME'):
|
||||
config[key] = config[key].decode(enc)
|
||||
return config
|
||||
|
||||
|
||||
def get_instance(args):
|
||||
markup = [a.strip().lower() for a in args.markup.split(',')]\
|
||||
if args.markup else None
|
||||
|
||||
settings = read_settings(args.settings)
|
||||
config_file = args.settings
|
||||
if config_file is None and os.path.isfile(DEFAULT_CONFIG_NAME):
|
||||
config_file = DEFAULT_CONFIG_NAME
|
||||
|
||||
cls = settings.get('PELICAN_CLASS')
|
||||
if isinstance(cls, basestring):
|
||||
settings = read_settings(config_file, override=get_config(args))
|
||||
|
||||
cls = settings['PELICAN_CLASS']
|
||||
if isinstance(cls, six.string_types):
|
||||
module, cls_name = cls.rsplit('.', 1)
|
||||
module = __import__(module)
|
||||
cls = getattr(module, cls_name)
|
||||
|
||||
return cls(settings, args.path, args.theme, args.output, markup,
|
||||
args.delete_outputdir)
|
||||
return cls(settings), settings
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_arguments()
|
||||
init(args.verbosity)
|
||||
# Split the markup languages only if some have been given. Otherwise,
|
||||
# populate the variable with None.
|
||||
pelican = get_instance(args)
|
||||
pelican, settings = get_instance(args)
|
||||
readers = Readers(settings)
|
||||
|
||||
watchers = {'content': folder_watcher(pelican.path,
|
||||
readers.extensions,
|
||||
pelican.ignore_files),
|
||||
'theme': folder_watcher(pelican.theme,
|
||||
[''],
|
||||
pelican.ignore_files),
|
||||
'settings': file_watcher(args.settings)}
|
||||
|
||||
for static_path in settings.get("STATIC_PATHS", []):
|
||||
watchers[static_path] = folder_watcher(static_path, [''], pelican.ignore_files)
|
||||
|
||||
try:
|
||||
if args.autoreload:
|
||||
files_found_error = True
|
||||
print(' --- AutoReload Mode: Monitoring `content`, `theme` and'
|
||||
' `settings` for changes. ---')
|
||||
|
||||
def _ignore_cache(pelican_obj):
|
||||
if pelican_obj.settings['AUTORELOAD_IGNORE_CACHE']:
|
||||
pelican_obj.settings['LOAD_CONTENT_CACHE'] = False
|
||||
|
||||
while True:
|
||||
try:
|
||||
# Check source dir for changed files ending with the given
|
||||
|
|
@ -292,40 +378,56 @@ def main():
|
|||
# restriction; all files are recursively checked if they
|
||||
# have changed, no matter what extension the filenames
|
||||
# have.
|
||||
if files_changed(pelican.path, pelican.markup) or \
|
||||
files_changed(pelican.theme, ['']):
|
||||
if not files_found_error:
|
||||
files_found_error = True
|
||||
pelican.run()
|
||||
modified = {k: next(v) for k, v in watchers.items()}
|
||||
original_load_cache = settings['LOAD_CONTENT_CACHE']
|
||||
|
||||
# reload also if settings.py changed
|
||||
if file_changed(args.settings):
|
||||
logger.info('%s changed, re-generating' %
|
||||
args.settings)
|
||||
pelican = get_instance(args)
|
||||
pelican.run()
|
||||
if modified['settings']:
|
||||
pelican, settings = get_instance(args)
|
||||
original_load_cache = settings['LOAD_CONTENT_CACHE']
|
||||
_ignore_cache(pelican)
|
||||
|
||||
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()
|
||||
# restore original caching policy
|
||||
pelican.settings['LOAD_CONTENT_CACHE'] = original_load_cache
|
||||
|
||||
time.sleep(.5) # sleep to avoid cpu load
|
||||
except KeyboardInterrupt:
|
||||
logger.warning("Keyboard interrupt, quitting.")
|
||||
break
|
||||
except NoFilesError:
|
||||
if files_found_error:
|
||||
logger.warning("No valid files found in content. "
|
||||
"Nothing to generate.")
|
||||
files_found_error = False
|
||||
time.sleep(1) # sleep to avoid cpu load
|
||||
except Exception, e:
|
||||
logger.warning(
|
||||
"Caught exception \"{}\". Reloading.".format(e)
|
||||
)
|
||||
continue
|
||||
else:
|
||||
pelican.run()
|
||||
except Exception, e:
|
||||
logger.critical(unicode(e))
|
||||
|
||||
if (args.verbosity == logging.DEBUG):
|
||||
except Exception as e:
|
||||
if (args.verbosity == logging.DEBUG):
|
||||
logger.critical(e.args)
|
||||
raise
|
||||
logger.warning(
|
||||
'Caught exception "%s". Reloading.', e)
|
||||
|
||||
finally:
|
||||
time.sleep(.5) # sleep to avoid cpu load
|
||||
|
||||
else:
|
||||
if next(watchers['content']) is None:
|
||||
logger.warning('No valid files found in content.')
|
||||
|
||||
if next(watchers['theme']) is None:
|
||||
logger.warning('Empty theme folder. Using `basic` theme.')
|
||||
|
||||
pelican.run()
|
||||
|
||||
except Exception as e:
|
||||
logger.critical('%s', e)
|
||||
|
||||
if args.verbosity == logging.DEBUG:
|
||||
raise
|
||||
else:
|
||||
sys.exit(getattr(e, 'exitcode', 1))
|
||||
|
|
|
|||
|
|
@ -1,46 +1,64 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals, print_function
|
||||
import six
|
||||
from six.moves.urllib.parse import (unquote, urlparse, urlunparse)
|
||||
|
||||
import copy
|
||||
import locale
|
||||
import logging
|
||||
import functools
|
||||
|
||||
from datetime import datetime
|
||||
from os import getenv
|
||||
from sys import platform, stdin
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
from pelican.settings import _DEFAULT_CONFIG
|
||||
from pelican.utils import slugify, truncate_html_words
|
||||
from pelican import signals
|
||||
from pelican.settings import DEFAULT_CONFIG
|
||||
from pelican.utils import (slugify, truncate_html_words, memoized, strftime,
|
||||
python_2_unicode_compatible, deprecated_attribute,
|
||||
path_to_url, set_date_tzinfo, SafeDatetime)
|
||||
|
||||
# Import these so that they're avalaible when you import from pelican.contents.
|
||||
from pelican.urlwrappers import (URLWrapper, Author, Category, Tag) # NOQA
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Page(object):
|
||||
"""Represents a page
|
||||
Given a content, and metadata, create an adequate object.
|
||||
|
||||
class Content(object):
|
||||
"""Represents a content.
|
||||
|
||||
:param content: the string to parse, containing the original content.
|
||||
:param metadata: the metadata associated to this page (optional).
|
||||
:param settings: the settings dictionary (optional).
|
||||
:param source_path: The location of the source of this content (if any).
|
||||
:param context: The shared context between generators.
|
||||
|
||||
"""
|
||||
mandatory_properties = ('title',)
|
||||
default_template = 'page'
|
||||
@deprecated_attribute(old='filename', new='source_path', since=(3, 2, 0))
|
||||
def filename():
|
||||
return None
|
||||
|
||||
def __init__(self, content, metadata=None, settings=None,
|
||||
filename=None):
|
||||
# init parameters
|
||||
if not metadata:
|
||||
source_path=None, context=None):
|
||||
if metadata is None:
|
||||
metadata = {}
|
||||
if not settings:
|
||||
settings = copy.deepcopy(_DEFAULT_CONFIG)
|
||||
if settings is None:
|
||||
settings = copy.deepcopy(DEFAULT_CONFIG)
|
||||
|
||||
self.settings = settings
|
||||
self._content = content
|
||||
if context is None:
|
||||
context = {}
|
||||
self._context = context
|
||||
self.translations = []
|
||||
|
||||
local_metadata = dict(settings.get('DEFAULT_METADATA', ()))
|
||||
local_metadata = dict(settings['DEFAULT_METADATA'])
|
||||
local_metadata.update(metadata)
|
||||
|
||||
# set metadata as attributes
|
||||
for key, value in local_metadata.items():
|
||||
if key in ('save_as', 'url'):
|
||||
key = 'override_' + key
|
||||
setattr(self, key.lower(), value)
|
||||
|
||||
# also keep track of the metadata attributes available
|
||||
|
|
@ -49,15 +67,18 @@ class Page(object):
|
|||
#default template if it's not defined in page
|
||||
self.template = self._get_template()
|
||||
|
||||
# default author to the one in settings if not defined
|
||||
# First, read the authors from "authors", if not, fallback to "author"
|
||||
# and if not use the settings defined one, if any.
|
||||
if not hasattr(self, 'author'):
|
||||
if 'AUTHOR' in settings:
|
||||
if hasattr(self, 'authors'):
|
||||
self.author = self.authors[0]
|
||||
elif 'AUTHOR' in settings:
|
||||
self.author = Author(settings['AUTHOR'], settings)
|
||||
else:
|
||||
title = filename.decode('utf-8') if filename else self.title
|
||||
self.author = Author(getenv('USER', 'John Doe'), settings)
|
||||
logger.warning(u"Author of `{0}' unknown, assuming that his name is "
|
||||
"`{1}'".format(title, self.author))
|
||||
|
||||
if not hasattr(self, 'authors') and hasattr(self, 'author'):
|
||||
self.authors = [self.author]
|
||||
|
||||
# XXX Split all the following code into pieces, there is too much here.
|
||||
|
||||
# manage languages
|
||||
self.in_default_lang = True
|
||||
|
|
@ -68,12 +89,18 @@ class Page(object):
|
|||
|
||||
self.in_default_lang = (self.lang == default_lang)
|
||||
|
||||
# create the slug if not existing, from the title
|
||||
if not hasattr(self, 'slug') and hasattr(self, 'title'):
|
||||
self.slug = slugify(self.title)
|
||||
# create the slug if not existing, generate slug according to
|
||||
# setting of SLUG_ATTRIBUTE
|
||||
if not hasattr(self, 'slug'):
|
||||
if settings['SLUGIFY_SOURCE'] == 'title' and hasattr(self, 'title'):
|
||||
self.slug = slugify(self.title,
|
||||
settings.get('SLUG_SUBSTITUTIONS', ()))
|
||||
elif settings['SLUGIFY_SOURCE'] == 'basename' and source_path != None:
|
||||
basename = os.path.basename(os.path.splitext(source_path)[0])
|
||||
self.slug = slugify(basename,
|
||||
settings.get('SLUG_SUBSTITUTIONS', ()))
|
||||
|
||||
if filename:
|
||||
self.filename = filename
|
||||
self.source_path = source_path
|
||||
|
||||
# manage the date format
|
||||
if not hasattr(self, 'date_format'):
|
||||
|
|
@ -83,80 +110,207 @@ class Page(object):
|
|||
self.date_format = settings['DEFAULT_DATE_FORMAT']
|
||||
|
||||
if isinstance(self.date_format, tuple):
|
||||
locale.setlocale(locale.LC_ALL, self.date_format[0])
|
||||
locale_string = self.date_format[0]
|
||||
if sys.version_info < (3, ) and isinstance(locale_string,
|
||||
six.text_type):
|
||||
locale_string = locale_string.encode('ascii')
|
||||
locale.setlocale(locale.LC_ALL, locale_string)
|
||||
self.date_format = self.date_format[1]
|
||||
|
||||
if hasattr(self, 'date'):
|
||||
encoded_date = self.date.strftime(
|
||||
self.date_format.encode('ascii', 'xmlcharrefreplace'))
|
||||
# manage timezone
|
||||
default_timezone = settings.get('TIMEZONE', 'UTC')
|
||||
timezone = getattr(self, 'timezone', default_timezone)
|
||||
|
||||
if platform == 'win32':
|
||||
self.locale_date = encoded_date.decode(stdin.encoding)
|
||||
else:
|
||||
self.locale_date = encoded_date.decode('utf')
|
||||
if hasattr(self, 'date'):
|
||||
self.date = set_date_tzinfo(self.date, timezone)
|
||||
self.locale_date = strftime(self.date, self.date_format)
|
||||
|
||||
if hasattr(self, 'modified'):
|
||||
self.modified = set_date_tzinfo(self.modified, timezone)
|
||||
self.locale_modified = strftime(self.modified, self.date_format)
|
||||
|
||||
# manage status
|
||||
if not hasattr(self, 'status'):
|
||||
self.status = settings['DEFAULT_STATUS']
|
||||
if not settings['WITH_FUTURE_DATES']:
|
||||
if hasattr(self, 'date') and self.date > datetime.now():
|
||||
if hasattr(self, 'date') and self.date > SafeDatetime.now():
|
||||
self.status = 'draft'
|
||||
|
||||
# store the summary metadata if it is set
|
||||
if 'summary' in metadata:
|
||||
self._summary = metadata['summary']
|
||||
|
||||
signals.content_object_init.send(self.__class__, instance=self)
|
||||
signals.content_object_init.send(self)
|
||||
|
||||
def __str__(self):
|
||||
if self.source_path is None:
|
||||
return repr(self)
|
||||
elif six.PY3:
|
||||
return self.source_path or repr(self)
|
||||
else:
|
||||
return str(self.source_path.encode('utf-8', 'replace'))
|
||||
|
||||
def check_properties(self):
|
||||
"""test that each mandatory property is set."""
|
||||
"""Test mandatory properties are set."""
|
||||
for prop in self.mandatory_properties:
|
||||
if not hasattr(self, prop):
|
||||
raise NameError(prop)
|
||||
|
||||
@property
|
||||
def url_format(self):
|
||||
return {
|
||||
"""Returns the URL, formatted with the proper values"""
|
||||
metadata = copy.copy(self.metadata)
|
||||
path = self.metadata.get('path', self.get_relative_source_path())
|
||||
default_category = self.settings['DEFAULT_CATEGORY']
|
||||
slug_substitutions = self.settings.get('SLUG_SUBSTITUTIONS', ())
|
||||
metadata.update({
|
||||
'path': path_to_url(path),
|
||||
'slug': getattr(self, 'slug', ''),
|
||||
'lang': getattr(self, 'lang', 'en'),
|
||||
'date': getattr(self, 'date', datetime.now()),
|
||||
'author': self.author,
|
||||
'category': getattr(self, 'category', 'misc'),
|
||||
}
|
||||
'date': getattr(self, 'date', SafeDatetime.now()),
|
||||
'author': slugify(
|
||||
getattr(self, 'author', ''),
|
||||
slug_substitutions
|
||||
),
|
||||
'category': slugify(
|
||||
getattr(self, 'category', default_category),
|
||||
slug_substitutions
|
||||
)
|
||||
})
|
||||
return metadata
|
||||
|
||||
def _expand_settings(self, key):
|
||||
fq_key = ('%s_%s' % (self.__class__.__name__, key)).upper()
|
||||
return self.settings[fq_key].format(**self.url_format)
|
||||
|
||||
def get_url_setting(self, key):
|
||||
if hasattr(self, 'override_' + key):
|
||||
return getattr(self, 'override_' + key)
|
||||
key = key if self.in_default_lang else 'lang_%s' % key
|
||||
return self._expand_settings(key)
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
if hasattr(self, "_get_content"):
|
||||
def _update_content(self, content, siteurl):
|
||||
"""Update the content attribute.
|
||||
|
||||
Change all the relative paths of the content to relative paths
|
||||
suitable for the output content.
|
||||
|
||||
:param content: content resource that will be passed to the templates.
|
||||
:param siteurl: siteurl which is locally generated by the writer in
|
||||
case of RELATIVE_URLS.
|
||||
"""
|
||||
if not content:
|
||||
return content
|
||||
|
||||
instrasite_link_regex = self.settings['INTRASITE_LINK_REGEX']
|
||||
regex = r"""
|
||||
(?P<markup><\s*[^\>]* # match tag with all url-value attributes
|
||||
(?:href|src|poster|data|cite|formaction|action)\s*=)
|
||||
|
||||
(?P<quote>["\']) # require value to be quoted
|
||||
(?P<path>{0}(?P<value>.*?)) # the url value
|
||||
\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 = Category(path, self.settings).url
|
||||
elif what == 'tag':
|
||||
origin = Tag(path, self.settings).url
|
||||
|
||||
# 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)
|
||||
|
||||
@memoized
|
||||
def get_content(self, siteurl):
|
||||
if hasattr(self, '_get_content'):
|
||||
content = self._get_content()
|
||||
else:
|
||||
content = self._content
|
||||
return content
|
||||
return self._update_content(content, siteurl)
|
||||
|
||||
def get_siteurl(self):
|
||||
return self._context.get('localsiteurl', '')
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
return self.get_content(self.get_siteurl())
|
||||
|
||||
def _get_summary(self):
|
||||
"""Returns the summary of an article, based on the summary metadata
|
||||
if it is set, else truncate the content."""
|
||||
"""Returns the summary of an article.
|
||||
|
||||
This is based on the summary metadata if set, otherwise truncate the
|
||||
content.
|
||||
"""
|
||||
if hasattr(self, '_summary'):
|
||||
return self._summary
|
||||
else:
|
||||
if self.settings['SUMMARY_MAX_LENGTH']:
|
||||
return truncate_html_words(self.content, self.settings['SUMMARY_MAX_LENGTH'])
|
||||
return self._update_content(self._summary,
|
||||
self.get_siteurl())
|
||||
|
||||
if self.settings['SUMMARY_MAX_LENGTH'] is None:
|
||||
return self.content
|
||||
|
||||
def _set_summary(self, summary):
|
||||
return truncate_html_words(self.content,
|
||||
self.settings['SUMMARY_MAX_LENGTH'])
|
||||
|
||||
@memoized
|
||||
def get_summary(self, siteurl):
|
||||
"""uses siteurl to be memoizable"""
|
||||
return self._get_summary()
|
||||
|
||||
@property
|
||||
def summary(self):
|
||||
return self.get_summary(self.get_siteurl())
|
||||
|
||||
@summary.setter
|
||||
def summary(self, value):
|
||||
"""Dummy function"""
|
||||
pass
|
||||
|
||||
summary = property(_get_summary, _set_summary, "Summary of the article."
|
||||
"Based on the content. Can't be set")
|
||||
|
||||
url = property(functools.partial(get_url_setting, key='url'))
|
||||
save_as = property(functools.partial(get_url_setting, key='save_as'))
|
||||
|
||||
|
|
@ -166,68 +320,132 @@ class Page(object):
|
|||
else:
|
||||
return self.default_template
|
||||
|
||||
def get_relative_source_path(self, source_path=None):
|
||||
"""Return the relative path (from the content path) to the given
|
||||
source_path.
|
||||
|
||||
If no source path is specified, use the source path of this
|
||||
content object.
|
||||
"""
|
||||
if not source_path:
|
||||
source_path = self.source_path
|
||||
if source_path is None:
|
||||
return None
|
||||
|
||||
return os.path.relpath(
|
||||
os.path.abspath(os.path.join(self.settings['PATH'], source_path)),
|
||||
os.path.abspath(self.settings['PATH'])
|
||||
)
|
||||
|
||||
@property
|
||||
def relative_dir(self):
|
||||
return os.path.dirname(os.path.relpath(
|
||||
os.path.abspath(self.source_path),
|
||||
os.path.abspath(self.settings['PATH']))
|
||||
)
|
||||
|
||||
|
||||
class Page(Content):
|
||||
mandatory_properties = ('title',)
|
||||
default_template = 'page'
|
||||
|
||||
|
||||
class Article(Page):
|
||||
mandatory_properties = ('title', 'date', 'category')
|
||||
default_template = 'article'
|
||||
|
||||
|
||||
class Draft(Page):
|
||||
mandatory_properties = ('title', 'category')
|
||||
default_template = 'article'
|
||||
|
||||
|
||||
class Quote(Page):
|
||||
base_properties = ('author', 'date')
|
||||
|
||||
|
||||
class URLWrapper(object):
|
||||
def __init__(self, name, settings):
|
||||
self.name = unicode(name)
|
||||
self.slug = slugify(self.name)
|
||||
self.settings = settings
|
||||
@python_2_unicode_compatible
|
||||
class Static(Page):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Static, self).__init__(*args, **kwargs)
|
||||
self._output_location_referenced = False
|
||||
|
||||
def as_dict(self):
|
||||
return self.__dict__
|
||||
@deprecated_attribute(old='filepath', new='source_path', since=(3, 2, 0))
|
||||
def filepath():
|
||||
return None
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
@deprecated_attribute(old='src', new='source_path', since=(3, 2, 0))
|
||||
def src():
|
||||
return None
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.name == unicode(other)
|
||||
@deprecated_attribute(old='dst', new='save_as', since=(3, 2, 0))
|
||||
def dst():
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name.encode('utf-8', 'replace'))
|
||||
@property
|
||||
def url(self):
|
||||
# Note when url has been referenced, so we can avoid overriding it.
|
||||
self._output_location_referenced = True
|
||||
return super(Static, self).url
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
@property
|
||||
def save_as(self):
|
||||
# Note when save_as has been referenced, so we can avoid overriding it.
|
||||
self._output_location_referenced = True
|
||||
return super(Static, self).save_as
|
||||
|
||||
def _from_settings(self, key):
|
||||
setting = "%s_%s" % (self.__class__.__name__.upper(), key)
|
||||
value = self.settings[setting]
|
||||
if not isinstance(value, basestring):
|
||||
logger.warning(u'%s is set to %s' % (setting, value))
|
||||
return value
|
||||
else:
|
||||
return unicode(value).format(**self.as_dict())
|
||||
def attach_to(self, content):
|
||||
"""Override our output directory with that of the given content object.
|
||||
"""
|
||||
# Determine our file's new output path relative to the linking document.
|
||||
# If it currently lives beneath the linking document's source directory,
|
||||
# preserve that relationship on output. Otherwise, make it a sibling.
|
||||
linking_source_dir = os.path.dirname(content.source_path)
|
||||
tail_path = os.path.relpath(self.source_path, linking_source_dir)
|
||||
if tail_path.startswith(os.pardir + os.sep):
|
||||
tail_path = os.path.basename(tail_path)
|
||||
new_save_as = os.path.join(
|
||||
os.path.dirname(content.save_as), tail_path)
|
||||
|
||||
url = property(functools.partial(_from_settings, key='URL'))
|
||||
save_as = property(functools.partial(_from_settings, key='SAVE_AS'))
|
||||
# We do not build our new url by joining tail_path with the linking
|
||||
# document's url, because we cannot know just by looking at the latter
|
||||
# whether it points to the document itself or to its parent directory.
|
||||
# (An url like 'some/content' might mean a directory named 'some'
|
||||
# with a file named 'content', or it might mean a directory named
|
||||
# 'some/content' with a file named 'index.html'.) Rather than trying
|
||||
# to figure it out by comparing the linking document's url and save_as
|
||||
# path, we simply build our new url from our new save_as path.
|
||||
new_url = path_to_url(new_save_as)
|
||||
|
||||
def _log_reason(reason):
|
||||
logger.warning("The {attach} link in %s cannot relocate %s "
|
||||
"because %s. Falling back to {filename} link behavior instead.",
|
||||
content.get_relative_source_path(),
|
||||
self.get_relative_source_path(), reason,
|
||||
extra={'limit_msg': "More {attach} warnings silenced."})
|
||||
|
||||
class Category(URLWrapper):
|
||||
pass
|
||||
# We never override an override, because we don't want to interfere
|
||||
# with user-defined overrides that might be in EXTRA_PATH_METADATA.
|
||||
if hasattr(self, 'override_save_as') or hasattr(self, 'override_url'):
|
||||
if new_save_as != self.save_as or new_url != self.url:
|
||||
_log_reason("its output location was already overridden")
|
||||
return
|
||||
|
||||
# We never change an output path that has already been referenced,
|
||||
# because we don't want to break links that depend on that path.
|
||||
if self._output_location_referenced:
|
||||
if new_save_as != self.save_as or new_url != self.url:
|
||||
_log_reason("another link already referenced its location")
|
||||
return
|
||||
|
||||
class Tag(URLWrapper):
|
||||
def __init__(self, name, *args, **kwargs):
|
||||
super(Tag, self).__init__(unicode.strip(name), *args, **kwargs)
|
||||
|
||||
|
||||
class Author(URLWrapper):
|
||||
pass
|
||||
self.override_save_as = new_save_as
|
||||
self.override_url = new_url
|
||||
|
||||
|
||||
def is_valid_content(content, f):
|
||||
try:
|
||||
content.check_properties()
|
||||
return True
|
||||
except NameError, e:
|
||||
logger.error(u"Skipping %s: impossible to find informations about '%s'"\
|
||||
% (f, e))
|
||||
except NameError as e:
|
||||
logger.error("Skipping %s: could not find information about '%s'", f, e)
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -1,67 +1,84 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
import os
|
||||
import six
|
||||
import math
|
||||
import random
|
||||
import logging
|
||||
import datetime
|
||||
import subprocess
|
||||
import shutil
|
||||
import fnmatch
|
||||
import calendar
|
||||
|
||||
from codecs import open
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
from itertools import chain
|
||||
from itertools import chain, groupby
|
||||
from operator import attrgetter, itemgetter
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader, PrefixLoader, ChoiceLoader
|
||||
from jinja2.exceptions import TemplateNotFound
|
||||
from jinja2 import (Environment, FileSystemLoader, PrefixLoader, ChoiceLoader,
|
||||
BaseLoader, TemplateNotFound)
|
||||
|
||||
from pelican.contents import Article, Page, Category, is_valid_content
|
||||
from pelican.readers import read_file
|
||||
from pelican.utils import copy, process_translations
|
||||
from pelican.contents import Article, Draft, Page, Static, is_valid_content
|
||||
from pelican.readers import Readers
|
||||
from pelican.utils import (copy, process_translations, mkdir_p, DateFormatter,
|
||||
FileStampDataCacher, python_2_unicode_compatible)
|
||||
from pelican import signals
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Generator(object):
|
||||
"""Baseclass generator"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
for idx, item in enumerate(('context', 'settings', 'path', 'theme',
|
||||
'output_path', 'markup')):
|
||||
setattr(self, item, args[idx])
|
||||
def __init__(self, context, settings, path, theme, output_path,
|
||||
readers_cache_name='', **kwargs):
|
||||
self.context = context
|
||||
self.settings = settings
|
||||
self.path = path
|
||||
self.theme = theme
|
||||
self.output_path = output_path
|
||||
|
||||
for arg, value in kwargs.items():
|
||||
setattr(self, arg, value)
|
||||
|
||||
self.readers = Readers(self.settings, readers_cache_name)
|
||||
|
||||
# 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.get('EXTRA_TEMPLATES_PATHS', [])
|
||||
|
||||
os.path.join(self.theme, 'templates')))
|
||||
self._templates_path += self.settings['EXTRA_TEMPLATES_PATHS']
|
||||
|
||||
theme_path = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
simple_loader = FileSystemLoader(os.path.join(theme_path,
|
||||
"themes", "simple", "templates"))
|
||||
self.env = Environment(
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
loader=ChoiceLoader([
|
||||
FileSystemLoader(self._templates_path),
|
||||
simple_loader, # implicit inheritance
|
||||
PrefixLoader({'!simple': simple_loader}) # explicit one
|
||||
]),
|
||||
extensions=self.settings.get('JINJA_EXTENSIONS', []),
|
||||
extensions=self.settings['JINJA_EXTENSIONS'],
|
||||
)
|
||||
|
||||
logger.debug('template list: {0}'.format(self.env.list_templates()))
|
||||
logger.debug('Template list: %s', self.env.list_templates())
|
||||
|
||||
# provide utils.strftime as a jinja filter
|
||||
self.env.filters.update({'strftime': DateFormatter()})
|
||||
|
||||
# get custom Jinja filters from user settings
|
||||
custom_filters = self.settings.get('JINJA_FILTERS', {})
|
||||
custom_filters = self.settings['JINJA_FILTERS']
|
||||
self.env.filters.update(custom_filters)
|
||||
|
||||
signals.generator_init.send(self)
|
||||
|
||||
def get_template(self, name):
|
||||
"""Return the template by name.
|
||||
Use self.theme to get the templates to use, and return a list of
|
||||
|
|
@ -71,34 +88,88 @@ class Generator(object):
|
|||
try:
|
||||
self._templates[name] = self.env.get_template(name + '.html')
|
||||
except TemplateNotFound:
|
||||
raise Exception('[templates] unable to load %s.html from %s' \
|
||||
% (name, self._templates_path))
|
||||
raise Exception('[templates] unable to load %s.html from %s'
|
||||
% (name, self._templates_path))
|
||||
return self._templates[name]
|
||||
|
||||
def get_files(self, path, exclude=[], extensions=None):
|
||||
def _include_path(self, path, extensions=None):
|
||||
"""Inclusion logic for .get_files(), returns True/False
|
||||
|
||||
:param path: the path which might be including
|
||||
:param extensions: the list of allowed extensions (if False, all
|
||||
extensions are allowed)
|
||||
"""
|
||||
if extensions is None:
|
||||
extensions = tuple(self.readers.extensions)
|
||||
basename = os.path.basename(path)
|
||||
|
||||
#check IGNORE_FILES
|
||||
ignores = self.settings['IGNORE_FILES']
|
||||
if any(fnmatch.fnmatch(basename, ignore) for ignore in ignores):
|
||||
return False
|
||||
|
||||
if extensions is False or basename.endswith(extensions):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_files(self, paths, exclude=[], extensions=None):
|
||||
"""Return a list of files to use, based on rules
|
||||
|
||||
:param path: the path to search the file on
|
||||
:param paths: the list pf paths to search (relative to self.path)
|
||||
:param exclude: the list of path to exclude
|
||||
:param extensions: the list of allowed extensions (if False, all
|
||||
extensions are allowed)
|
||||
"""
|
||||
if not extensions:
|
||||
extensions = self.markup
|
||||
if isinstance(paths, six.string_types):
|
||||
paths = [paths] # backward compatibility for older generators
|
||||
|
||||
# group the exclude dir names by parent path, for use with os.walk()
|
||||
exclusions_by_dirpath = {}
|
||||
for e in exclude:
|
||||
parent_path, subdir = os.path.split(os.path.join(self.path, e))
|
||||
exclusions_by_dirpath.setdefault(parent_path, set()).add(subdir)
|
||||
|
||||
files = []
|
||||
for path in paths:
|
||||
# careful: os.path.join() will add a slash when path == ''.
|
||||
root = os.path.join(self.path, path) if path else self.path
|
||||
|
||||
try:
|
||||
iter = os.walk(path, followlinks=True)
|
||||
except TypeError: # python 2.5 does not support followlinks
|
||||
iter = os.walk(path)
|
||||
|
||||
for root, dirs, temp_files in iter:
|
||||
for e in exclude:
|
||||
if e in dirs:
|
||||
dirs.remove(e)
|
||||
files.extend([os.sep.join((root, f)) for f in temp_files
|
||||
if True in [f.endswith(ext) for ext in extensions]])
|
||||
if os.path.isdir(root):
|
||||
for dirpath, dirs, temp_files in os.walk(root, followlinks=True):
|
||||
for e in exclusions_by_dirpath.get(dirpath, ()):
|
||||
if e in dirs:
|
||||
dirs.remove(e)
|
||||
reldir = os.path.relpath(dirpath, self.path)
|
||||
for f in temp_files:
|
||||
fp = os.path.join(reldir, f)
|
||||
if self._include_path(fp, extensions):
|
||||
files.append(fp)
|
||||
elif os.path.exists(root) and self._include_path(path, extensions):
|
||||
files.append(path) # can't walk non-directories
|
||||
return files
|
||||
|
||||
def add_source_path(self, content):
|
||||
"""Record a source file path that a Generator found and processed.
|
||||
Store a reference to its Content object, for url lookups later.
|
||||
"""
|
||||
location = content.get_relative_source_path()
|
||||
self.context['filenames'][location] = content
|
||||
|
||||
def _add_failed_source_path(self, path):
|
||||
"""Record a source file path that a Generator failed to process.
|
||||
(For example, one that was missing mandatory metadata.)
|
||||
The path argument is expected to be relative to self.path.
|
||||
"""
|
||||
self.context['filenames'][os.path.normpath(path)] = None
|
||||
|
||||
def _is_potential_source_path(self, path):
|
||||
"""Return True if path was supposed to be used as a source file.
|
||||
(This includes all source files that have been found by generators
|
||||
before this method is called, even if they failed to process.)
|
||||
The path argument is expected to be relative to self.path.
|
||||
"""
|
||||
return os.path.normpath(path) in self.context['filenames']
|
||||
|
||||
def _update_context(self, items):
|
||||
"""Update the context with the given items from the currrent
|
||||
processor.
|
||||
|
|
@ -106,11 +177,74 @@ class Generator(object):
|
|||
for item in items:
|
||||
value = getattr(self, item)
|
||||
if hasattr(value, 'items'):
|
||||
value = value.items()
|
||||
value = list(value.items()) # py3k safeguard for iterators
|
||||
self.context[item] = value
|
||||
|
||||
def __str__(self):
|
||||
# return the name of the class for logging purposes
|
||||
return self.__class__.__name__
|
||||
|
||||
class ArticlesGenerator(Generator):
|
||||
|
||||
class CachingGenerator(Generator, FileStampDataCacher):
|
||||
'''Subclass of Generator and FileStampDataCacher classes
|
||||
|
||||
enables content caching, either at the generator or reader level
|
||||
'''
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
'''Initialize the generator, then set up caching
|
||||
|
||||
note the multiple inheritance structure
|
||||
'''
|
||||
cls_name = self.__class__.__name__
|
||||
Generator.__init__(self, *args,
|
||||
readers_cache_name=(cls_name + '-Readers'),
|
||||
**kwargs)
|
||||
|
||||
cache_this_level = self.settings['CONTENT_CACHING_LAYER'] == 'generator'
|
||||
caching_policy = cache_this_level and self.settings['CACHE_CONTENT']
|
||||
load_policy = cache_this_level and self.settings['LOAD_CONTENT_CACHE']
|
||||
FileStampDataCacher.__init__(self, self.settings, cls_name,
|
||||
caching_policy, load_policy
|
||||
)
|
||||
|
||||
def _get_file_stamp(self, filename):
|
||||
'''Get filestamp for path relative to generator.path'''
|
||||
filename = os.path.join(self.path, filename)
|
||||
return super(CachingGenerator, self)._get_file_stamp(filename)
|
||||
|
||||
|
||||
class _FileLoader(BaseLoader):
|
||||
|
||||
def __init__(self, path, basedir):
|
||||
self.path = path
|
||||
self.fullpath = os.path.join(basedir, path)
|
||||
|
||||
def get_source(self, environment, template):
|
||||
if template != self.path or not os.path.exists(self.fullpath):
|
||||
raise TemplateNotFound(template)
|
||||
mtime = os.path.getmtime(self.fullpath)
|
||||
with open(self.fullpath, 'r', encoding='utf-8') as f:
|
||||
source = f.read()
|
||||
return (source, self.fullpath,
|
||||
lambda: mtime == os.path.getmtime(self.fullpath))
|
||||
|
||||
|
||||
class TemplatePagesGenerator(Generator):
|
||||
|
||||
def generate_output(self, writer):
|
||||
for source, dest in self.settings['TEMPLATE_PAGES'].items():
|
||||
self.env.loader.loaders.insert(0, _FileLoader(source, self.path))
|
||||
try:
|
||||
template = self.env.get_template(source)
|
||||
rurls = self.settings['RELATIVE_URLS']
|
||||
writer.write_file(dest, template, self.context, rurls,
|
||||
override_output=True)
|
||||
finally:
|
||||
del self.env.loader.loaders[0]
|
||||
|
||||
|
||||
class ArticlesGenerator(CachingGenerator):
|
||||
"""Generate blog articles"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
@ -122,18 +256,13 @@ class ArticlesGenerator(Generator):
|
|||
self.categories = defaultdict(list)
|
||||
self.related_posts = []
|
||||
self.authors = defaultdict(list)
|
||||
self.drafts = [] # only drafts in default language
|
||||
self.drafts_translations = []
|
||||
super(ArticlesGenerator, self).__init__(*args, **kwargs)
|
||||
self.drafts = []
|
||||
signals.article_generator_init.send(self)
|
||||
|
||||
def generate_feeds(self, writer):
|
||||
"""Generate the feeds from the current context, and output files."""
|
||||
if self.settings.get('FEED_ATOM') is None and self.settings.get('FEED_RSS') is None:
|
||||
return
|
||||
elif self.settings.get('SITEURL') is '':
|
||||
logger.warning(
|
||||
'Feeds generated without SITEURL set properly may not be valid'
|
||||
)
|
||||
|
||||
if self.settings.get('FEED_ATOM'):
|
||||
writer.write_feed(self.articles, self.context,
|
||||
|
|
@ -143,60 +272,157 @@ class ArticlesGenerator(Generator):
|
|||
writer.write_feed(self.articles, self.context,
|
||||
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)
|
||||
|
||||
if self.settings.get('FEED_ALL_ATOM'):
|
||||
writer.write_feed(all_articles, self.context,
|
||||
self.settings['FEED_ALL_ATOM'])
|
||||
|
||||
if self.settings.get('FEED_ALL_RSS'):
|
||||
writer.write_feed(all_articles, self.context,
|
||||
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)
|
||||
self.settings['CATEGORY_FEED_ATOM']
|
||||
% cat.slug)
|
||||
|
||||
if self.settings.get('CATEGORY_FEED_RSS'):
|
||||
writer.write_feed(arts, self.context,
|
||||
self.settings['CATEGORY_FEED_RSS'] % cat,
|
||||
feed_type='rss')
|
||||
self.settings['CATEGORY_FEED_RSS']
|
||||
% cat.slug, feed_type='rss')
|
||||
|
||||
if self.settings.get('TAG_FEED_ATOM') or self.settings.get('TAG_FEED_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)
|
||||
|
||||
if self.settings.get('AUTHOR_FEED_RSS'):
|
||||
writer.write_feed(arts, self.context,
|
||||
self.settings['AUTHOR_FEED_RSS']
|
||||
% auth.slug, 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)
|
||||
self.settings['TAG_FEED_ATOM']
|
||||
% tag.slug)
|
||||
|
||||
if self.settings.get('TAG_FEED_RSS'):
|
||||
writer.write_feed(arts, self.context,
|
||||
self.settings['TAG_FEED_RSS'] % tag,
|
||||
self.settings['TAG_FEED_RSS'] % tag.slug,
|
||||
feed_type='rss')
|
||||
|
||||
if self.settings.get('TRANSLATION_FEED'):
|
||||
if (self.settings.get('TRANSLATION_FEED_ATOM')
|
||||
or self.settings.get('TRANSLATION_FEED_RSS')):
|
||||
translations_feeds = defaultdict(list)
|
||||
for article in chain(self.articles, self.translations):
|
||||
translations_feeds[article.lang].append(article)
|
||||
|
||||
for lang, items in translations_feeds.items():
|
||||
items.sort(key=attrgetter('date'), reverse=True)
|
||||
writer.write_feed(items, self.context,
|
||||
self.settings['TRANSLATION_FEED'] % lang)
|
||||
if self.settings.get('TRANSLATION_FEED_ATOM'):
|
||||
writer.write_feed(
|
||||
items, self.context,
|
||||
self.settings['TRANSLATION_FEED_ATOM'] % lang)
|
||||
if self.settings.get('TRANSLATION_FEED_RSS'):
|
||||
writer.write_feed(
|
||||
items, self.context,
|
||||
self.settings['TRANSLATION_FEED_RSS'] % lang,
|
||||
feed_type='rss')
|
||||
|
||||
def generate_articles(self, write):
|
||||
"""Generate the articles."""
|
||||
for article in chain(self.translations, self.articles):
|
||||
signals.article_generator_write_article.send(self, content=article)
|
||||
write(article.save_as, self.get_template(article.template),
|
||||
self.context, article=article, category=article.category)
|
||||
self.context, article=article, category=article.category,
|
||||
override_output=hasattr(article, 'override_save_as'))
|
||||
|
||||
def generate_period_archives(self, write):
|
||||
"""Generate per-year, per-month, and per-day archives."""
|
||||
try:
|
||||
template = self.get_template('period_archives')
|
||||
except Exception:
|
||||
template = self.get_template('archives')
|
||||
|
||||
period_save_as = {
|
||||
'year': self.settings['YEAR_ARCHIVE_SAVE_AS'],
|
||||
'month': self.settings['MONTH_ARCHIVE_SAVE_AS'],
|
||||
'day': self.settings['DAY_ARCHIVE_SAVE_AS'],
|
||||
}
|
||||
|
||||
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):
|
||||
"""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)
|
||||
# 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)
|
||||
context = self.context.copy()
|
||||
|
||||
if key == period_date_key['year']:
|
||||
context["period"] = (_period,)
|
||||
else:
|
||||
month_name = calendar.month_name[_period[1]]
|
||||
if not six.PY3:
|
||||
month_name = month_name.decode('utf-8')
|
||||
if key == period_date_key['month']:
|
||||
context["period"] = (_period[0],
|
||||
month_name)
|
||||
else:
|
||||
context["period"] = (_period[0],
|
||||
month_name,
|
||||
_period[2])
|
||||
|
||||
write(save_as, template, context,
|
||||
dates=archive, blog=True)
|
||||
|
||||
for period in 'year', 'month', 'day':
|
||||
save_as = period_save_as[period]
|
||||
if save_as:
|
||||
key = period_date_key[period]
|
||||
_generate_period_archives(self.dates, key, save_as)
|
||||
|
||||
def generate_direct_templates(self, write):
|
||||
"""Generate direct templates pages"""
|
||||
PAGINATED_TEMPLATES = self.settings.get('PAGINATED_DIRECT_TEMPLATES')
|
||||
for template in self.settings.get('DIRECT_TEMPLATES'):
|
||||
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)
|
||||
'%s.html' % template)
|
||||
if not save_as:
|
||||
continue
|
||||
|
||||
write(save_as, self.get_template(template),
|
||||
self.context, blog=True, paginated=paginated,
|
||||
page_name=template)
|
||||
page_name=os.path.splitext(save_as)[0])
|
||||
|
||||
def generate_tags(self, write):
|
||||
"""Generate Tags pages."""
|
||||
|
|
@ -205,48 +431,51 @@ class ArticlesGenerator(Generator):
|
|||
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},
|
||||
page_name=u'tag/%s' % tag)
|
||||
articles=articles, dates=dates,
|
||||
paginated={'articles': articles, 'dates': dates},
|
||||
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,
|
||||
category=cat, articles=articles, dates=dates,
|
||||
paginated={'articles': articles, 'dates': dates},
|
||||
page_name=u'category/%s' % cat)
|
||||
category=cat, articles=articles, dates=dates,
|
||||
paginated={'articles': articles, 'dates': dates},
|
||||
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},
|
||||
page_name=u'author/%s' % aut)
|
||||
author=aut, articles=articles, dates=dates,
|
||||
paginated={'articles': articles, 'dates': dates},
|
||||
page_name=aut.page_name, all_articles=self.articles)
|
||||
|
||||
def generate_drafts(self, write):
|
||||
"""Generate drafts pages."""
|
||||
for article in self.drafts:
|
||||
write('drafts/%s.html' % article.slug,
|
||||
self.get_template(article.template), self.context,
|
||||
article=article, category=article.category)
|
||||
for draft in chain(self.drafts_translations, self.drafts):
|
||||
write(draft.save_as, self.get_template(draft.template),
|
||||
self.context, article=draft, category=draft.category,
|
||||
override_output=hasattr(draft, 'override_save_as'),
|
||||
all_articles=self.articles)
|
||||
|
||||
def generate_pages(self, writer):
|
||||
"""Generate the pages on the disk"""
|
||||
write = partial(writer.write_file,
|
||||
relative_urls=self.settings.get('RELATIVE_URLS'))
|
||||
relative_urls=self.settings['RELATIVE_URLS'])
|
||||
|
||||
# to minimize the number of relative path stuff modification
|
||||
# in writer, articles pass first
|
||||
self.generate_articles(write)
|
||||
self.generate_period_archives(write)
|
||||
self.generate_direct_templates(write)
|
||||
|
||||
|
||||
# and subfolders after that
|
||||
self.generate_tags(write)
|
||||
self.generate_categories(write)
|
||||
|
|
@ -254,71 +483,75 @@ class ArticlesGenerator(Generator):
|
|||
self.generate_drafts(write)
|
||||
|
||||
def generate_context(self):
|
||||
"""change the context"""
|
||||
"""Add the articles into the shared context"""
|
||||
|
||||
article_path = os.path.normpath( # we have to remove trailing slashes
|
||||
os.path.join(self.path, self.settings['ARTICLE_DIR'])
|
||||
)
|
||||
all_articles = []
|
||||
all_drafts = []
|
||||
for f in self.get_files(
|
||||
article_path,
|
||||
self.settings['ARTICLE_PATHS'],
|
||||
exclude=self.settings['ARTICLE_EXCLUDES']):
|
||||
try:
|
||||
content, metadata = read_file(f, settings=self.settings)
|
||||
except Exception, e:
|
||||
logger.warning(u'Could not process %s\n%s' % (f, str(e)))
|
||||
continue
|
||||
article = self.get_cached_data(f, None)
|
||||
if article is None:
|
||||
try:
|
||||
article = self.readers.read_file(
|
||||
base_path=self.path, path=f, content_class=Article,
|
||||
context=self.context,
|
||||
preread_signal=signals.article_generator_preread,
|
||||
preread_sender=self,
|
||||
context_signal=signals.article_generator_context,
|
||||
context_sender=self)
|
||||
except Exception as e:
|
||||
logger.error('Could not process %s\n%s', f, e,
|
||||
exc_info=self.settings.get('DEBUG', False))
|
||||
self._add_failed_source_path(f)
|
||||
continue
|
||||
|
||||
# if no category is set, use the name of the path as a category
|
||||
if 'category' not in metadata:
|
||||
if not is_valid_content(article, f):
|
||||
self._add_failed_source_path(f)
|
||||
continue
|
||||
|
||||
if os.path.dirname(f) == article_path: # if the article is not in a subdirectory
|
||||
category = self.settings['DEFAULT_CATEGORY']
|
||||
else:
|
||||
category = os.path.basename(os.path.dirname(f))\
|
||||
.decode('utf-8')
|
||||
self.cache_data(f, article)
|
||||
|
||||
if category != '':
|
||||
metadata['category'] = Category(category, self.settings)
|
||||
self.add_source_path(article)
|
||||
|
||||
if 'date' not in metadata and self.settings['DEFAULT_DATE']:
|
||||
if self.settings['DEFAULT_DATE'] == 'fs':
|
||||
metadata['date'] = datetime.datetime.fromtimestamp(
|
||||
os.stat(f).st_ctime)
|
||||
else:
|
||||
metadata['date'] = datetime.datetime(
|
||||
*self.settings['DEFAULT_DATE'])
|
||||
|
||||
signals.article_generate_context.send(self, metadata=metadata)
|
||||
article = Article(content, metadata, settings=self.settings,
|
||||
filename=f)
|
||||
if not is_valid_content(article, f):
|
||||
continue
|
||||
|
||||
if article.status == "published":
|
||||
if hasattr(article, 'tags'):
|
||||
for tag in article.tags:
|
||||
self.tags[tag].append(article)
|
||||
if article.status.lower() == "published":
|
||||
all_articles.append(article)
|
||||
elif article.status == "draft":
|
||||
self.drafts.append(article)
|
||||
elif article.status.lower() == "draft":
|
||||
draft = self.readers.read_file(
|
||||
base_path=self.path, path=f, content_class=Draft,
|
||||
context=self.context,
|
||||
preread_signal=signals.article_generator_preread,
|
||||
preread_sender=self,
|
||||
context_signal=signals.article_generator_context,
|
||||
context_sender=self)
|
||||
all_drafts.append(draft)
|
||||
else:
|
||||
logger.warning(u"Unknown status %s for file %s, skipping it." %
|
||||
(repr(unicode.encode(article.status, 'utf-8')),
|
||||
repr(f)))
|
||||
logger.error("Unknown status '%s' for file %s, skipping it.",
|
||||
article.status, f)
|
||||
|
||||
self.articles, self.translations = process_translations(all_articles)
|
||||
self.articles, self.translations = process_translations(all_articles,
|
||||
order_by=self.settings['ARTICLE_ORDER_BY'])
|
||||
self.drafts, self.drafts_translations = \
|
||||
process_translations(all_drafts)
|
||||
|
||||
signals.article_generator_pretaxonomy.send(self)
|
||||
|
||||
for article in self.articles:
|
||||
# only main articles are listed in categories, not translations
|
||||
# only main articles are listed in categories and tags
|
||||
# not translations
|
||||
self.categories[article.category].append(article)
|
||||
self.authors[article.author].append(article)
|
||||
|
||||
if hasattr(article, 'tags'):
|
||||
for tag in article.tags:
|
||||
self.tags[tag].append(article)
|
||||
# ignore blank authors as well as undefined
|
||||
for author in getattr(article, 'authors', []):
|
||||
if author.name != '':
|
||||
self.authors[author].append(article)
|
||||
# sort the articles by date
|
||||
self.articles.sort(key=attrgetter('date'), reverse=True)
|
||||
self.dates = list(self.articles)
|
||||
self.dates.sort(key=attrgetter('date'),
|
||||
reverse=self.context['NEWEST_FIRST_ARCHIVES'])
|
||||
reverse=self.context['NEWEST_FIRST_ARCHIVES'])
|
||||
|
||||
# create tag cloud
|
||||
tag_cloud = defaultdict(int)
|
||||
|
|
@ -329,7 +562,7 @@ class ArticlesGenerator(Generator):
|
|||
tag_cloud = sorted(tag_cloud.items(), key=itemgetter(1), reverse=True)
|
||||
tag_cloud = tag_cloud[:self.settings.get('TAG_CLOUD_MAX_ITEMS')]
|
||||
|
||||
tags = map(itemgetter(1), tag_cloud)
|
||||
tags = list(map(itemgetter(1), tag_cloud))
|
||||
if tags:
|
||||
max_count = max(tags)
|
||||
steps = self.settings.get('TAG_CLOUD_STEPS')
|
||||
|
|
@ -351,21 +584,24 @@ class ArticlesGenerator(Generator):
|
|||
# order the categories per name
|
||||
self.categories = list(self.categories.items())
|
||||
self.categories.sort(
|
||||
key=lambda item: item[0].name,
|
||||
reverse=self.settings['REVERSE_CATEGORY_ORDER'])
|
||||
reverse=self.settings['REVERSE_CATEGORY_ORDER'])
|
||||
|
||||
self.authors = list(self.authors.items())
|
||||
self.authors.sort(key=lambda item: item[0].name)
|
||||
self.authors.sort()
|
||||
|
||||
self._update_context(('articles', 'dates', 'tags', 'categories',
|
||||
'tag_cloud', 'authors', 'related_posts'))
|
||||
self.save_cache()
|
||||
self.readers.save_cache()
|
||||
signals.article_generator_finalized.send(self)
|
||||
|
||||
def generate_output(self, writer):
|
||||
self.generate_feeds(writer)
|
||||
self.generate_pages(writer)
|
||||
signals.article_writer_finalized.send(self, writer=writer)
|
||||
|
||||
|
||||
class PagesGenerator(Generator):
|
||||
class PagesGenerator(CachingGenerator):
|
||||
"""Generate pages"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
@ -373,207 +609,137 @@ class PagesGenerator(Generator):
|
|||
self.hidden_pages = []
|
||||
self.hidden_translations = []
|
||||
super(PagesGenerator, self).__init__(*args, **kwargs)
|
||||
signals.pages_generator_init.send(self)
|
||||
signals.page_generator_init.send(self)
|
||||
|
||||
def generate_context(self):
|
||||
all_pages = []
|
||||
hidden_pages = []
|
||||
for f in self.get_files(
|
||||
os.path.join(self.path, self.settings['PAGE_DIR']),
|
||||
self.settings['PAGE_PATHS'],
|
||||
exclude=self.settings['PAGE_EXCLUDES']):
|
||||
try:
|
||||
content, metadata = read_file(f, settings=self.settings)
|
||||
except Exception, e:
|
||||
logger.warning(u'Could not process %s\n%s' % (f, str(e)))
|
||||
continue
|
||||
signals.pages_generate_context.send(self, metadata=metadata )
|
||||
page = Page(content, metadata, settings=self.settings,
|
||||
filename=f)
|
||||
if not is_valid_content(page, f):
|
||||
continue
|
||||
page = self.get_cached_data(f, None)
|
||||
if page is None:
|
||||
try:
|
||||
page = self.readers.read_file(
|
||||
base_path=self.path, path=f, content_class=Page,
|
||||
context=self.context,
|
||||
preread_signal=signals.page_generator_preread,
|
||||
preread_sender=self,
|
||||
context_signal=signals.page_generator_context,
|
||||
context_sender=self)
|
||||
except Exception as e:
|
||||
logger.error('Could not process %s\n%s', f, e,
|
||||
exc_info=self.settings.get('DEBUG', False))
|
||||
self._add_failed_source_path(f)
|
||||
continue
|
||||
|
||||
if not is_valid_content(page, f):
|
||||
self._add_failed_source_path(f)
|
||||
continue
|
||||
|
||||
self.cache_data(f, page)
|
||||
|
||||
self.add_source_path(page)
|
||||
|
||||
if page.status == "published":
|
||||
all_pages.append(page)
|
||||
elif page.status == "hidden":
|
||||
hidden_pages.append(page)
|
||||
else:
|
||||
logger.warning(u"Unknown status %s for file %s, skipping it." %
|
||||
(repr(unicode.encode(page.status, 'utf-8')),
|
||||
repr(f)))
|
||||
logger.error("Unknown status '%s' for file %s, skipping it.",
|
||||
page.status, f)
|
||||
|
||||
self.pages, self.translations = process_translations(all_pages)
|
||||
self.hidden_pages, self.hidden_translations = process_translations(hidden_pages)
|
||||
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))
|
||||
|
||||
self._update_context(('pages', ))
|
||||
self.context['PAGES'] = self.pages
|
||||
|
||||
self.save_cache()
|
||||
self.readers.save_cache()
|
||||
signals.page_generator_finalized.send(self)
|
||||
|
||||
def generate_output(self, writer):
|
||||
for page in chain(self.translations, self.pages,
|
||||
self.hidden_translations, self.hidden_pages):
|
||||
writer.write_file(page.save_as, self.get_template(page.template),
|
||||
self.context, page=page,
|
||||
relative_urls=self.settings.get('RELATIVE_URLS'))
|
||||
self.hidden_translations, self.hidden_pages):
|
||||
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'))
|
||||
|
||||
|
||||
class StaticGenerator(Generator):
|
||||
"""copy static paths (what you want to copy, like images, medias etc.
|
||||
to output"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(StaticGenerator, self).__init__(*args, **kwargs)
|
||||
signals.static_generator_init.send(self)
|
||||
|
||||
def _copy_paths(self, paths, source, destination, output_path,
|
||||
final_path=None):
|
||||
final_path=None):
|
||||
"""Copy all the paths from source to destination"""
|
||||
for path in paths:
|
||||
copy(path, source, os.path.join(output_path, destination),
|
||||
final_path, overwrite=True)
|
||||
if final_path:
|
||||
copy(os.path.join(source, path),
|
||||
os.path.join(output_path, destination, final_path))
|
||||
else:
|
||||
copy(os.path.join(source, path),
|
||||
os.path.join(output_path, destination, path))
|
||||
|
||||
def generate_context(self):
|
||||
self.staticfiles = []
|
||||
for f in self.get_files(self.settings['STATIC_PATHS'],
|
||||
exclude=self.settings['STATIC_EXCLUDES'],
|
||||
extensions=False):
|
||||
|
||||
if self.settings['WEBASSETS']:
|
||||
from webassets import Environment as AssetsEnvironment
|
||||
# skip content source files unless the user explicitly wants them
|
||||
if self.settings['STATIC_EXCLUDE_SOURCES']:
|
||||
if self._is_potential_source_path(f):
|
||||
continue
|
||||
|
||||
# Define the assets environment that will be passed to the
|
||||
# generators. The StaticGenerator must then be run first to have
|
||||
# the assets in the output_path before generating the templates.
|
||||
|
||||
# Let ASSET_URL honor Pelican's RELATIVE_URLS setting.
|
||||
# Hint for templates:
|
||||
# Current version of webassets seem to remove any relative
|
||||
# paths at the beginning of the URL. So, if RELATIVE_URLS
|
||||
# is on, ASSET_URL will start with 'theme/', regardless if we
|
||||
# set assets_url here to './theme/' or to 'theme/'.
|
||||
# XXX However, this breaks the ASSET_URL if user navigates to
|
||||
# a sub-URL, e.g. if he clicks on a category. To workaround this
|
||||
# issue, I use
|
||||
# <link rel="stylesheet" href="{{ SITEURL }}/{{ ASSET_URL }}">
|
||||
# instead of
|
||||
# <link rel="stylesheet" href="{{ ASSET_URL }}">
|
||||
if self.settings.get('RELATIVE_URLS'):
|
||||
assets_url = './theme/'
|
||||
else:
|
||||
assets_url = self.settings['SITEURL'] + '/theme/'
|
||||
assets_src = os.path.join(self.output_path, 'theme')
|
||||
self.assets_env = AssetsEnvironment(assets_src, assets_url)
|
||||
|
||||
if logging.getLevelName(logger.getEffectiveLevel()) == "DEBUG":
|
||||
self.assets_env.debug = True
|
||||
static = self.readers.read_file(
|
||||
base_path=self.path, path=f, content_class=Static,
|
||||
fmt='static', context=self.context,
|
||||
preread_signal=signals.static_generator_preread,
|
||||
preread_sender=self,
|
||||
context_signal=signals.static_generator_context,
|
||||
context_sender=self)
|
||||
self.staticfiles.append(static)
|
||||
self.add_source_path(static)
|
||||
self._update_context(('staticfiles',))
|
||||
signals.static_generator_finalized.send(self)
|
||||
|
||||
def generate_output(self, writer):
|
||||
|
||||
self._copy_paths(self.settings['STATIC_PATHS'], self.path,
|
||||
'static', self.output_path)
|
||||
self._copy_paths(self.settings['THEME_STATIC_PATHS'], self.theme,
|
||||
'theme', self.output_path, '.')
|
||||
self.settings['THEME_STATIC_DIR'], self.output_path,
|
||||
os.curdir)
|
||||
# copy all Static files
|
||||
for sc in self.context['staticfiles']:
|
||||
source_path = os.path.join(self.path, sc.source_path)
|
||||
save_as = os.path.join(self.output_path, sc.save_as)
|
||||
mkdir_p(os.path.dirname(save_as))
|
||||
shutil.copy2(source_path, save_as)
|
||||
logger.info('Copying %s to %s', sc.source_path, sc.save_as)
|
||||
|
||||
# copy all the files needed
|
||||
for source, destination in self.settings['FILES_TO_COPY']:
|
||||
copy(source, self.path, self.output_path, destination,
|
||||
overwrite=True)
|
||||
|
||||
|
||||
class PdfGenerator(Generator):
|
||||
"""Generate PDFs on the output dir, for all articles and pages coming from
|
||||
rst"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PdfGenerator, self).__init__(*args, **kwargs)
|
||||
try:
|
||||
from rst2pdf.createpdf import RstToPdf
|
||||
pdf_style_path = os.path.join(self.settings['PDF_STYLE_PATH']) \
|
||||
if 'PDF_STYLE_PATH' in self.settings.keys() \
|
||||
else ''
|
||||
pdf_style = self.settings['PDF_STYLE'] if 'PDF_STYLE' \
|
||||
in self.settings.keys() \
|
||||
else 'twelvepoint'
|
||||
self.pdfcreator = RstToPdf(breakside=0,
|
||||
stylesheets=[pdf_style],
|
||||
style_path=[pdf_style_path])
|
||||
except ImportError:
|
||||
raise Exception("unable to find rst2pdf")
|
||||
|
||||
def _create_pdf(self, obj, output_path):
|
||||
if obj.filename.endswith(".rst"):
|
||||
filename = obj.slug + ".pdf"
|
||||
output_pdf = os.path.join(output_path, filename)
|
||||
# print "Generating pdf for", obj.filename, " in ", output_pdf
|
||||
with open(obj.filename) as f:
|
||||
self.pdfcreator.createPdf(text=f.read(), output=output_pdf)
|
||||
logger.info(u' [ok] writing %s' % output_pdf)
|
||||
|
||||
def generate_context(self):
|
||||
pass
|
||||
|
||||
def generate_output(self, writer=None):
|
||||
# we don't use the writer passed as argument here
|
||||
# since we write our own files
|
||||
logger.info(u' Generating PDF files...')
|
||||
pdf_path = os.path.join(self.output_path, 'pdf')
|
||||
if not os.path.exists(pdf_path):
|
||||
try:
|
||||
os.mkdir(pdf_path)
|
||||
except OSError:
|
||||
logger.error("Couldn't create the pdf output folder in " + pdf_path)
|
||||
pass
|
||||
|
||||
for article in self.context['articles']:
|
||||
self._create_pdf(article, pdf_path)
|
||||
|
||||
for page in self.context['pages']:
|
||||
self._create_pdf(page, pdf_path)
|
||||
|
||||
class SourceFileGenerator(Generator):
|
||||
|
||||
def generate_context(self):
|
||||
self.output_extension = self.settings['OUTPUT_SOURCES_EXTENSION']
|
||||
|
||||
def _create_source(self, obj, output_path):
|
||||
filename = os.path.splitext(obj.save_as)[0]
|
||||
dest = os.path.join(output_path, filename + self.output_extension)
|
||||
copy('', obj.filename, dest)
|
||||
def _create_source(self, obj):
|
||||
output_path, _ = os.path.splitext(obj.save_as)
|
||||
dest = os.path.join(self.output_path,
|
||||
output_path + self.output_extension)
|
||||
copy(obj.source_path, dest)
|
||||
|
||||
def generate_output(self, writer=None):
|
||||
logger.info(u' Generating source files...')
|
||||
for object in chain(self.context['articles'], self.context['pages']):
|
||||
self._create_source(object, self.output_path)
|
||||
|
||||
class LessCSSGenerator(Generator):
|
||||
"""Compile less css files."""
|
||||
|
||||
def _compile(self, less_file, source_dir, dest_dir):
|
||||
base = os.path.relpath(less_file, source_dir)
|
||||
target = os.path.splitext(
|
||||
os.path.join(dest_dir, base))[0] + '.css'
|
||||
target_dir = os.path.dirname(target)
|
||||
|
||||
if not os.path.exists(target_dir):
|
||||
try:
|
||||
os.makedirs(target_dir)
|
||||
except OSError:
|
||||
logger.error("Couldn't create the less css output folder in " +
|
||||
target_dir)
|
||||
|
||||
subprocess.call([self._lessc, less_file, target])
|
||||
logger.info(u' [ok] compiled %s' % base)
|
||||
|
||||
def generate_output(self, writer=None):
|
||||
logger.info(u' Compiling less css')
|
||||
|
||||
# store out compiler here, so it won't be evaulted on each run of
|
||||
# _compile
|
||||
lg = self.settings['LESS_GENERATOR']
|
||||
self._lessc = lg if isinstance(lg, basestring) else 'lessc'
|
||||
|
||||
# walk static paths
|
||||
for static_path in self.settings['STATIC_PATHS']:
|
||||
for f in self.get_files(
|
||||
os.path.join(self.path, static_path),
|
||||
extensions=['less']):
|
||||
|
||||
self._compile(f, self.path, self.output_path)
|
||||
|
||||
# walk theme static paths
|
||||
theme_output_path = os.path.join(self.output_path, 'theme')
|
||||
|
||||
for static_path in self.settings['THEME_STATIC_PATHS']:
|
||||
theme_static_path = os.path.join(self.theme, static_path)
|
||||
for f in self.get_files(
|
||||
theme_static_path,
|
||||
extensions=['less']):
|
||||
|
||||
self._compile(f, theme_static_path, theme_output_path)
|
||||
logger.info('Generating source files...')
|
||||
for obj in chain(self.context['articles'], self.context['pages']):
|
||||
self._create_source(obj)
|
||||
for obj_trans in obj.translations:
|
||||
self._create_source(obj_trans)
|
||||
|
|
|
|||
202
pelican/log.py
202
pelican/log.py
|
|
@ -1,3 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
__all__ = [
|
||||
'init'
|
||||
]
|
||||
|
|
@ -5,66 +8,175 @@ __all__ = [
|
|||
import os
|
||||
import sys
|
||||
import logging
|
||||
import locale
|
||||
|
||||
from logging import Formatter, getLogger, StreamHandler, DEBUG
|
||||
from collections import defaultdict, Mapping
|
||||
|
||||
import six
|
||||
|
||||
RESET_TERM = u'\033[0;m'
|
||||
|
||||
COLOR_CODES = {
|
||||
'red': 31,
|
||||
'yellow': 33,
|
||||
'cyan': 36,
|
||||
'white': 37,
|
||||
'bgred': 41,
|
||||
'bggrey': 100,
|
||||
}
|
||||
|
||||
|
||||
def ansi(color, text):
|
||||
"""Wrap text in an ansi escape sequence"""
|
||||
code = COLOR_CODES[color]
|
||||
return u'\033[1;{0}m{1}{2}'.format(code, text, RESET_TERM)
|
||||
|
||||
|
||||
class ANSIFormatter(Formatter):
|
||||
"""
|
||||
Convert a `logging.LogReport' object into colored text, using ANSI escape sequences.
|
||||
"""
|
||||
## colors:
|
||||
class BaseFormatter(logging.Formatter):
|
||||
def __init__(self, fmt=None, datefmt=None):
|
||||
FORMAT = '%(customlevelname)s %(message)s'
|
||||
super(BaseFormatter, self).__init__(fmt=FORMAT, datefmt=datefmt)
|
||||
|
||||
def format(self, record):
|
||||
if record.levelname is 'INFO':
|
||||
return ansi('cyan', '-> ') + unicode(record.msg)
|
||||
elif record.levelname is 'WARNING':
|
||||
return ansi('yellow', record.levelname) + ': ' + unicode(record.msg)
|
||||
elif record.levelname is 'ERROR':
|
||||
return ansi('red', record.levelname) + ': ' + unicode(record.msg)
|
||||
elif record.levelname is 'CRITICAL':
|
||||
return ansi('bgred', record.levelname) + ': ' + unicode(record.msg)
|
||||
elif record.levelname is 'DEBUG':
|
||||
return ansi('bggrey', record.levelname) + ': ' + unicode(record.msg)
|
||||
record.__dict__['customlevelname'] = self._get_levelname(record.levelname)
|
||||
# format multiline messages 'nicely' to make it clear they are together
|
||||
record.msg = record.msg.replace('\n', '\n | ')
|
||||
return super(BaseFormatter, self).format(record)
|
||||
|
||||
def formatException(self, ei):
|
||||
''' prefix traceback info for better representation '''
|
||||
# .formatException returns a bytestring in py2 and unicode in py3
|
||||
# since .format will handle unicode conversion,
|
||||
# str() calls are used to normalize formatting string
|
||||
s = super(BaseFormatter, self).formatException(ei)
|
||||
# fancy format traceback
|
||||
s = str('\n').join(str(' | ') + line for line in s.splitlines())
|
||||
# separate the traceback from the preceding lines
|
||||
s = str(' |___\n{}').format(s)
|
||||
return s
|
||||
|
||||
def _get_levelname(self, name):
|
||||
''' NOOP: overridden by subclasses '''
|
||||
return name
|
||||
|
||||
|
||||
class ANSIFormatter(BaseFormatter):
|
||||
ANSI_CODES = {
|
||||
'red': '\033[1;31m',
|
||||
'yellow': '\033[1;33m',
|
||||
'cyan': '\033[1;36m',
|
||||
'white': '\033[1;37m',
|
||||
'bgred': '\033[1;41m',
|
||||
'bggrey': '\033[1;100m',
|
||||
'reset': '\033[0;m'}
|
||||
|
||||
LEVEL_COLORS = {
|
||||
'INFO': 'cyan',
|
||||
'WARNING': 'yellow',
|
||||
'ERROR': 'red',
|
||||
'CRITICAL': 'bgred',
|
||||
'DEBUG': 'bggrey'}
|
||||
|
||||
def _get_levelname(self, name):
|
||||
color = self.ANSI_CODES[self.LEVEL_COLORS.get(name, 'white')]
|
||||
if name == 'INFO':
|
||||
fmt = '{0}->{2}'
|
||||
else:
|
||||
return ansi('white', record.levelname) + ': ' + unicode(record.msg)
|
||||
fmt = '{0}{1}{2}:'
|
||||
return fmt.format(color, name, self.ANSI_CODES['reset'])
|
||||
|
||||
|
||||
class TextFormatter(Formatter):
|
||||
class TextFormatter(BaseFormatter):
|
||||
"""
|
||||
Convert a `logging.LogReport' object into text.
|
||||
Convert a `logging.LogRecord' object into text.
|
||||
"""
|
||||
|
||||
def format(self, record):
|
||||
if not record.levelname or record.levelname is 'INFO':
|
||||
return record.msg
|
||||
def _get_levelname(self, name):
|
||||
if name == 'INFO':
|
||||
return '->'
|
||||
else:
|
||||
return record.levelname + ': ' + record.msg
|
||||
return name + ':'
|
||||
|
||||
|
||||
def init(level=None, logger=getLogger(), handler=StreamHandler()):
|
||||
class LimitFilter(logging.Filter):
|
||||
"""
|
||||
Remove duplicates records, and limit the number of records in the same
|
||||
group.
|
||||
|
||||
Groups are specified by the message to use when the number of records in
|
||||
the same group hit the limit.
|
||||
E.g.: log.warning(('43 is not the answer', 'More erroneous answers'))
|
||||
"""
|
||||
|
||||
_ignore = set()
|
||||
_threshold = 5
|
||||
_group_count = defaultdict(int)
|
||||
|
||||
def filter(self, record):
|
||||
# don't limit log messages for anything above "warning"
|
||||
if record.levelno > logging.WARN:
|
||||
return True
|
||||
|
||||
# extract group
|
||||
group = record.__dict__.get('limit_msg', None)
|
||||
group_args = record.__dict__.get('limit_args', ())
|
||||
|
||||
# ignore record if it was already raised
|
||||
# use .getMessage() and not .msg for string formatting
|
||||
ignore_key = (record.levelno, record.getMessage())
|
||||
if ignore_key in self._ignore:
|
||||
return False
|
||||
else:
|
||||
self._ignore.add(ignore_key)
|
||||
|
||||
# check if we went over threshold
|
||||
if group:
|
||||
key = (record.levelno, group)
|
||||
self._group_count[key] += 1
|
||||
if self._group_count[key] == self._threshold:
|
||||
record.msg = group
|
||||
record.args = group_args
|
||||
elif self._group_count[key] > self._threshold:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class SafeLogger(logging.Logger):
|
||||
"""
|
||||
Base Logger which properly encodes Exceptions in Py2
|
||||
"""
|
||||
_exc_encoding = locale.getpreferredencoding()
|
||||
|
||||
def _log(self, level, msg, args, exc_info=None, extra=None):
|
||||
# if the only argument is a Mapping, Logger uses that for formatting
|
||||
# format values for that case
|
||||
if args and len(args)==1 and isinstance(args[0], Mapping):
|
||||
args = ({k: self._decode_arg(v) for k, v in args[0].items()},)
|
||||
# otherwise, format each arg
|
||||
else:
|
||||
args = tuple(self._decode_arg(arg) for arg in args)
|
||||
super(SafeLogger, self)._log(level, msg, args,
|
||||
exc_info=exc_info, extra=extra)
|
||||
|
||||
def _decode_arg(self, arg):
|
||||
'''
|
||||
properly decode an arg for Py2 if it's Exception
|
||||
|
||||
|
||||
localized systems have errors in native language if locale is set
|
||||
so convert the message to unicode with the correct encoding
|
||||
'''
|
||||
if isinstance(arg, Exception):
|
||||
text = str(arg)
|
||||
if six.PY2:
|
||||
text = text.decode(self._exc_encoding)
|
||||
return text
|
||||
else:
|
||||
return arg
|
||||
|
||||
|
||||
class LimitLogger(SafeLogger):
|
||||
"""
|
||||
A logger which adds LimitFilter automatically
|
||||
"""
|
||||
|
||||
limit_filter = LimitFilter()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(LimitLogger, self).__init__(*args, **kwargs)
|
||||
self.addFilter(LimitLogger.limit_filter)
|
||||
|
||||
logging.setLoggerClass(LimitLogger)
|
||||
|
||||
|
||||
def init(level=None, handler=logging.StreamHandler()):
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
if os.isatty(sys.stdout.fileno()) \
|
||||
and not sys.platform.startswith('win'):
|
||||
if (os.isatty(sys.stdout.fileno())
|
||||
and not sys.platform.startswith('win')):
|
||||
fmt = ANSIFormatter()
|
||||
else:
|
||||
fmt = TextFormatter()
|
||||
|
|
@ -76,7 +188,7 @@ def init(level=None, logger=getLogger(), handler=StreamHandler()):
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
init(level=DEBUG)
|
||||
init(level=logging.DEBUG)
|
||||
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.debug('debug')
|
||||
|
|
|
|||
|
|
@ -1,12 +1,37 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals, print_function
|
||||
import six
|
||||
|
||||
# From django.core.paginator
|
||||
from collections import namedtuple
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
|
||||
from math import ceil
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
PaginationRule = namedtuple(
|
||||
'PaginationRule',
|
||||
'min_page URL SAVE_AS',
|
||||
)
|
||||
|
||||
|
||||
class Paginator(object):
|
||||
def __init__(self, object_list, per_page, orphans=0):
|
||||
def __init__(self, name, object_list, settings):
|
||||
self.name = name
|
||||
self.object_list = object_list
|
||||
self.per_page = per_page
|
||||
self.orphans = orphans
|
||||
self.settings = settings
|
||||
|
||||
if settings.get('DEFAULT_PAGINATION'):
|
||||
self.per_page = settings.get('DEFAULT_PAGINATION')
|
||||
self.orphans = settings.get('DEFAULT_ORPHANS')
|
||||
else:
|
||||
self.per_page = len(object_list)
|
||||
self.orphans = 0
|
||||
|
||||
self._num_pages = self._count = None
|
||||
|
||||
def page(self, number):
|
||||
|
|
@ -15,7 +40,8 @@ class Paginator(object):
|
|||
top = bottom + self.per_page
|
||||
if top + self.orphans >= self.count:
|
||||
top = self.count
|
||||
return Page(self.object_list[bottom:top], number, self)
|
||||
return Page(self.name, self.object_list[bottom:top], number, self,
|
||||
self.settings)
|
||||
|
||||
def _get_count(self):
|
||||
"Returns the total number of objects, across all pages."
|
||||
|
|
@ -37,15 +63,17 @@ class Paginator(object):
|
|||
Returns a 1-based range of pages for iterating through within
|
||||
a template for loop.
|
||||
"""
|
||||
return range(1, self.num_pages + 1)
|
||||
return list(range(1, self.num_pages + 1))
|
||||
page_range = property(_get_page_range)
|
||||
|
||||
|
||||
class Page(object):
|
||||
def __init__(self, object_list, number, paginator):
|
||||
def __init__(self, name, object_list, number, paginator, settings):
|
||||
self.name, self.extension = os.path.splitext(name)
|
||||
self.object_list = object_list
|
||||
self.number = number
|
||||
self.paginator = paginator
|
||||
self.settings = settings
|
||||
|
||||
def __repr__(self):
|
||||
return '<Page %s of %s>' % (self.number, self.paginator.num_pages)
|
||||
|
|
@ -84,3 +112,49 @@ class Page(object):
|
|||
if self.number == self.paginator.num_pages:
|
||||
return self.paginator.count
|
||||
return self.number * self.paginator.per_page
|
||||
|
||||
def _from_settings(self, key):
|
||||
"""Returns URL information as defined in settings. Similar to
|
||||
URLWrapper._from_settings, but specialized to deal with pagination
|
||||
logic."""
|
||||
|
||||
rule = None
|
||||
|
||||
# find the last matching pagination rule
|
||||
for p in self.settings['PAGINATION_PATTERNS']:
|
||||
if p.min_page <= self.number:
|
||||
rule = p
|
||||
|
||||
if not rule:
|
||||
return ''
|
||||
|
||||
prop_value = getattr(rule, key)
|
||||
|
||||
if not isinstance(prop_value, six.string_types):
|
||||
logger.warning('%s is set to %s', key, prop_value)
|
||||
return prop_value
|
||||
|
||||
# URL or SAVE_AS is a string, format it with a controlled context
|
||||
context = {
|
||||
'name': self.name,
|
||||
'object_list': self.object_list,
|
||||
'number': self.number,
|
||||
'paginator': self.paginator,
|
||||
'settings': self.settings,
|
||||
'base_name': os.path.dirname(self.name),
|
||||
'number_sep': '/',
|
||||
'extension': self.extension,
|
||||
}
|
||||
|
||||
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:]
|
||||
return ret
|
||||
|
||||
url = property(functools.partial(_from_settings, key='URL'))
|
||||
save_as = property(functools.partial(_from_settings, key='SAVE_AS'))
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) Marco Milanesi <kpanic@gnufunk.org>
|
||||
|
||||
A plugin to list your Github Activity
|
||||
To enable it set in your pelican config file the GITHUB_ACTIVITY_FEED
|
||||
parameter pointing to your github activity feed.
|
||||
|
||||
for example my personal activity feed is:
|
||||
|
||||
https://github.com/kpanic.atom
|
||||
|
||||
in your template just write a for in jinja2 syntax against the
|
||||
github_activity variable.
|
||||
|
||||
i.e.
|
||||
|
||||
<div class="social">
|
||||
<h2>Github Activity</h2>
|
||||
<ul>
|
||||
|
||||
{% for entry in github_activity %}
|
||||
<li><b>{{ entry[0] }}</b><br /> {{ entry[1] }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div><!-- /.github_activity -->
|
||||
|
||||
github_activity is a list containing a list. The first element is the title
|
||||
and the second element is the raw html from github
|
||||
"""
|
||||
|
||||
from pelican import signals
|
||||
|
||||
|
||||
class GitHubActivity():
|
||||
"""
|
||||
A class created to fetch github activity with feedparser
|
||||
"""
|
||||
def __init__(self, generator):
|
||||
try:
|
||||
import feedparser
|
||||
self.activities = feedparser.parse(
|
||||
generator.settings['GITHUB_ACTIVITY_FEED'])
|
||||
except ImportError:
|
||||
raise Exception("Unable to find feedparser")
|
||||
|
||||
def fetch(self):
|
||||
"""
|
||||
returns a list of html snippets fetched from github actitivy feed
|
||||
"""
|
||||
|
||||
entries = []
|
||||
for activity in self.activities['entries']:
|
||||
entries.append(
|
||||
[element for element in [activity['title'],
|
||||
activity['content'][0]['value']]])
|
||||
|
||||
return entries
|
||||
|
||||
|
||||
def fetch_github_activity(gen, metadata):
|
||||
"""
|
||||
registered handler for the github activity plugin
|
||||
it puts in generator.context the html needed to be displayed on a
|
||||
template
|
||||
"""
|
||||
|
||||
if 'GITHUB_ACTIVITY_FEED' in gen.settings.keys():
|
||||
gen.context['github_activity'] = gen.plugin_instance.fetch()
|
||||
|
||||
|
||||
def feed_parser_initialization(generator):
|
||||
"""
|
||||
Initialization of feed parser
|
||||
"""
|
||||
|
||||
generator.plugin_instance = GitHubActivity(generator)
|
||||
|
||||
|
||||
def register():
|
||||
"""
|
||||
Plugin registration
|
||||
"""
|
||||
signals.article_generator_init.connect(feed_parser_initialization)
|
||||
signals.article_generate_context.connect(fetch_github_activity)
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
from pelican import signals
|
||||
|
||||
"""
|
||||
License plugin for Pelican
|
||||
==========================
|
||||
|
||||
This plugin allows you to define a LICENSE setting and adds the contents of that
|
||||
license variable to the article's context, making that variable available to use
|
||||
from within your theme's templates.
|
||||
|
||||
Settings:
|
||||
---------
|
||||
|
||||
Define LICENSE in your settings file with the contents of your default license.
|
||||
|
||||
"""
|
||||
|
||||
def add_license(generator, metadata):
|
||||
if 'license' not in metadata.keys()\
|
||||
and 'LICENSE' in generator.settings.keys():
|
||||
metadata['license'] = generator.settings['LICENSE']
|
||||
|
||||
def register():
|
||||
signals.article_generate_context.connect(add_license)
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
import hashlib
|
||||
|
||||
from pelican import signals
|
||||
"""
|
||||
Gravatar plugin for Pelican
|
||||
===========================
|
||||
|
||||
This plugin assigns the ``author_gravatar`` variable to the Gravatar URL and
|
||||
makes the variable available within the article's context.
|
||||
|
||||
Settings:
|
||||
---------
|
||||
|
||||
Add AUTHOR_EMAIL to your settings file to define the default author's email
|
||||
address. Obviously, that email address must be associated with a Gravatar
|
||||
account.
|
||||
|
||||
Article metadata:
|
||||
------------------
|
||||
|
||||
:email: article's author email
|
||||
|
||||
If one of them are defined, the author_gravatar variable is added to the
|
||||
article's context.
|
||||
"""
|
||||
|
||||
def add_gravatar(generator, metadata):
|
||||
|
||||
#first check email
|
||||
if 'email' not in metadata.keys()\
|
||||
and 'AUTHOR_EMAIL' in generator.settings.keys():
|
||||
metadata['email'] = generator.settings['AUTHOR_EMAIL']
|
||||
|
||||
#then add gravatar url
|
||||
if 'email' in metadata.keys():
|
||||
gravatar_url = "http://www.gravatar.com/avatar/" + \
|
||||
hashlib.md5(metadata['email'].lower()).hexdigest()
|
||||
metadata["author_gravatar"] = gravatar_url
|
||||
|
||||
|
||||
def register():
|
||||
signals.article_generate_context.connect(add_gravatar)
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
from docutils import nodes
|
||||
from docutils.parsers.rst import directives, Directive
|
||||
from pelican import log
|
||||
|
||||
"""
|
||||
HTML tags for reStructuredText
|
||||
==============================
|
||||
|
||||
Directives
|
||||
----------
|
||||
|
||||
.. html::
|
||||
|
||||
(HTML code)
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
A search engine:
|
||||
|
||||
.. html::
|
||||
<form action="http://seeks.fr/search" method="GET">
|
||||
<input type="text" value="Pelican v2" title="Search" maxlength="2048" name="q" autocomplete="on" />
|
||||
<input type="hidden" name="lang" value="en" />
|
||||
<input type="submit" value="Seeks !" id="search_button" />
|
||||
</form>
|
||||
|
||||
|
||||
A contact form:
|
||||
|
||||
.. html::
|
||||
|
||||
<form method="GET" action="mailto:some email">
|
||||
<p>
|
||||
<input type="text" placeholder="Subject" name="subject">
|
||||
<br />
|
||||
<textarea name="body" placeholder="Message">
|
||||
</textarea>
|
||||
<br />
|
||||
<input type="reset"><input type="submit">
|
||||
</p>
|
||||
</form>
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class RawHtml(Directive):
|
||||
required_arguments = 0
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
has_content = True
|
||||
|
||||
def run(self):
|
||||
html = u' '.join(self.content)
|
||||
node = nodes.raw('', html, format='html')
|
||||
return [node]
|
||||
|
||||
|
||||
|
||||
def register():
|
||||
directives.register_directive('html', RawHtml)
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
from pelican import signals
|
||||
|
||||
def test(sender):
|
||||
print "%s initialized !!" % sender
|
||||
|
||||
def register():
|
||||
signals.initialized.connect(test)
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
from pelican import signals
|
||||
|
||||
"""
|
||||
Related posts plugin for Pelican
|
||||
================================
|
||||
|
||||
Adds related_posts variable to article's context
|
||||
|
||||
Settings
|
||||
--------
|
||||
To enable, add
|
||||
|
||||
from pelican.plugins import related_posts
|
||||
PLUGINS = [related_posts]
|
||||
|
||||
to your settings.py.
|
||||
|
||||
Usage
|
||||
-----
|
||||
{% if article.related_posts %}
|
||||
<ul>
|
||||
{% for related_post in article.related_posts %}
|
||||
<li>{{ related_post }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
|
||||
"""
|
||||
|
||||
related_posts = []
|
||||
|
||||
|
||||
def add_related_posts(generator, metadata):
|
||||
if 'tags' in metadata:
|
||||
for tag in metadata['tags']:
|
||||
#print tag
|
||||
for related_article in generator.tags[tag]:
|
||||
related_posts.append(related_article)
|
||||
|
||||
if len(related_posts) < 1:
|
||||
return
|
||||
|
||||
relation_score = dict(zip(set(related_posts), map(related_posts.count,
|
||||
set(related_posts))))
|
||||
ranked_related = sorted(relation_score, key=relation_score.get)
|
||||
|
||||
metadata["related_posts"] = ranked_related[:5]
|
||||
|
||||
|
||||
def register():
|
||||
signals.article_generate_context.connect(add_related_posts)
|
||||
|
|
@ -1,190 +0,0 @@
|
|||
import collections
|
||||
import os.path
|
||||
|
||||
from datetime import datetime
|
||||
from logging import warning, info
|
||||
from codecs import open
|
||||
|
||||
from pelican import signals, contents
|
||||
|
||||
TXT_HEADER = u"""{0}/index.html
|
||||
{0}/archives.html
|
||||
{0}/tags.html
|
||||
{0}/categories.html
|
||||
"""
|
||||
|
||||
XML_HEADER = u"""<?xml version="1.0" encoding="utf-8"?>
|
||||
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
|
||||
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
"""
|
||||
|
||||
XML_URL = u"""
|
||||
<url>
|
||||
<loc>{0}/{1}</loc>
|
||||
<lastmod>{2}</lastmod>
|
||||
<changefreq>{3}</changefreq>
|
||||
<priority>{4}</priority>
|
||||
</url>
|
||||
"""
|
||||
|
||||
XML_FOOTER = u"""
|
||||
</urlset>
|
||||
"""
|
||||
|
||||
|
||||
def format_date(date):
|
||||
if date.tzinfo:
|
||||
tz = date.strftime('%s')
|
||||
tz = tz[:-2] + ':' + tz[-2:]
|
||||
else:
|
||||
tz = "-00:00"
|
||||
return date.strftime("%Y-%m-%dT%H:%M:%S") + tz
|
||||
|
||||
|
||||
class SitemapGenerator(object):
|
||||
|
||||
def __init__(self, context, settings, path, theme, output_path, *null):
|
||||
|
||||
self.output_path = output_path
|
||||
self.context = context
|
||||
self.now = datetime.now()
|
||||
self.siteurl = settings.get('SITEURL')
|
||||
|
||||
self.format = 'xml'
|
||||
|
||||
self.changefreqs = {
|
||||
'articles': 'monthly',
|
||||
'indexes': 'daily',
|
||||
'pages': 'monthly'
|
||||
}
|
||||
|
||||
self.priorities = {
|
||||
'articles': 0.5,
|
||||
'indexes': 0.5,
|
||||
'pages': 0.5
|
||||
}
|
||||
|
||||
config = settings.get('SITEMAP', {})
|
||||
|
||||
if not isinstance(config, dict):
|
||||
warning("sitemap plugin: the SITEMAP setting must be a dict")
|
||||
else:
|
||||
fmt = config.get('format')
|
||||
pris = config.get('priorities')
|
||||
chfreqs = config.get('changefreqs')
|
||||
|
||||
if fmt not in ('xml', 'txt'):
|
||||
warning("sitemap plugin: SITEMAP['format'] must be `txt' or `xml'")
|
||||
warning("sitemap plugin: Setting SITEMAP['format'] on `xml'")
|
||||
elif fmt == 'txt':
|
||||
self.format = fmt
|
||||
return
|
||||
|
||||
valid_keys = ('articles', 'indexes', 'pages')
|
||||
valid_chfreqs = ('always', 'hourly', 'daily', 'weekly', 'monthly',
|
||||
'yearly', 'never')
|
||||
|
||||
if isinstance(pris, dict):
|
||||
for k, v in pris.iteritems():
|
||||
if k in valid_keys and not isinstance(v, (int, float)):
|
||||
default = self.priorities[k]
|
||||
warning("sitemap plugin: priorities must be numbers")
|
||||
warning("sitemap plugin: setting SITEMAP['priorities']"
|
||||
"['{0}'] on {1}".format(k, default))
|
||||
pris[k] = default
|
||||
self.priorities.update(pris)
|
||||
elif pris is not None:
|
||||
warning("sitemap plugin: SITEMAP['priorities'] must be a dict")
|
||||
warning("sitemap plugin: using the default values")
|
||||
|
||||
if isinstance(chfreqs, dict):
|
||||
for k, v in chfreqs.iteritems():
|
||||
if k in valid_keys and v not in valid_chfreqs:
|
||||
default = self.changefreqs[k]
|
||||
warning("sitemap plugin: invalid changefreq `{0}'".format(v))
|
||||
warning("sitemap plugin: setting SITEMAP['changefreqs']"
|
||||
"['{0}'] on '{1}'".format(k, default))
|
||||
chfreqs[k] = default
|
||||
self.changefreqs.update(chfreqs)
|
||||
elif chfreqs is not None:
|
||||
warning("sitemap plugin: SITEMAP['changefreqs'] must be a dict")
|
||||
warning("sitemap plugin: using the default values")
|
||||
|
||||
|
||||
|
||||
def write_url(self, page, fd):
|
||||
|
||||
if getattr(page, 'status', 'published') != 'published':
|
||||
return
|
||||
|
||||
page_path = os.path.join(self.output_path, page.url)
|
||||
if not os.path.exists(page_path):
|
||||
return
|
||||
|
||||
lastmod = format_date(getattr(page, 'date', self.now))
|
||||
|
||||
if isinstance(page, contents.Article):
|
||||
pri = self.priorities['articles']
|
||||
chfreq = self.changefreqs['articles']
|
||||
elif isinstance(page, contents.Page):
|
||||
pri = self.priorities['pages']
|
||||
chfreq = self.changefreqs['pages']
|
||||
else:
|
||||
pri = self.priorities['indexes']
|
||||
chfreq = self.changefreqs['indexes']
|
||||
|
||||
|
||||
if self.format == 'xml':
|
||||
fd.write(XML_URL.format(self.siteurl, page.url, lastmod, chfreq, pri))
|
||||
else:
|
||||
fd.write(self.siteurl + '/' + loc + '\n')
|
||||
|
||||
|
||||
def generate_output(self, writer):
|
||||
path = os.path.join(self.output_path, 'sitemap.{0}'.format(self.format))
|
||||
|
||||
pages = self.context['pages'] + self.context['articles'] \
|
||||
+ [ c for (c, a) in self.context['categories']] \
|
||||
+ [ t for (t, a) in self.context['tags']] \
|
||||
+ [ a for (a, b) in self.context['authors']]
|
||||
|
||||
for article in self.context['articles']:
|
||||
pages += article.translations
|
||||
|
||||
info('writing {0}'.format(path))
|
||||
|
||||
with open(path, 'w', encoding='utf-8') as fd:
|
||||
|
||||
if self.format == 'xml':
|
||||
fd.write(XML_HEADER)
|
||||
else:
|
||||
fd.write(TXT_HEADER.format(self.siteurl))
|
||||
|
||||
FakePage = collections.namedtuple('FakePage',
|
||||
['status',
|
||||
'date',
|
||||
'url'])
|
||||
|
||||
for standard_page_url in ['index.html',
|
||||
'archives.html',
|
||||
'tags.html',
|
||||
'categories.html']:
|
||||
fake = FakePage(status='published',
|
||||
date=self.now,
|
||||
url=standard_page_url)
|
||||
self.write_url(fake, fd)
|
||||
|
||||
for page in pages:
|
||||
self.write_url(page, fd)
|
||||
|
||||
if self.format == 'xml':
|
||||
fd.write(XML_FOOTER)
|
||||
|
||||
|
||||
def get_generators(generators):
|
||||
return SitemapGenerator
|
||||
|
||||
|
||||
def register():
|
||||
signals.get_generators.connect(get_generators)
|
||||
|
|
@ -1,45 +1,76 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
try:
|
||||
import docutils
|
||||
import docutils.core
|
||||
import docutils.io
|
||||
from docutils.writers.html4css1 import HTMLTranslator
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
# import the directives to have pygments support
|
||||
from pelican import rstdirectives # NOQA
|
||||
except ImportError:
|
||||
core = False
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
import docutils
|
||||
import docutils.core
|
||||
import docutils.io
|
||||
from docutils.writers.html4css1 import HTMLTranslator
|
||||
import six
|
||||
|
||||
# import the directives to have pygments support
|
||||
from pelican import rstdirectives # NOQA
|
||||
try:
|
||||
from markdown import Markdown
|
||||
except ImportError:
|
||||
Markdown = False # NOQA
|
||||
import re
|
||||
try:
|
||||
from html import escape
|
||||
except ImportError:
|
||||
from cgi import escape
|
||||
from six.moves.html_parser import HTMLParser
|
||||
|
||||
from pelican.contents import Category, Tag, Author
|
||||
from pelican.utils import get_date, pelican_open
|
||||
from pelican import signals
|
||||
from pelican.contents import Page, Category, Tag, Author
|
||||
from pelican.utils import get_date, pelican_open, FileStampDataCacher, SafeDatetime
|
||||
|
||||
|
||||
_METADATA_PROCESSORS = {
|
||||
'tags': lambda x, y: [Tag(tag, y) for tag in unicode(x).split(',')],
|
||||
METADATA_PROCESSORS = {
|
||||
'tags': lambda x, y: [Tag(tag, y) for tag in x.split(',')],
|
||||
'date': lambda x, y: get_date(x),
|
||||
'status': lambda x, y: unicode.strip(x),
|
||||
'modified': lambda x, y: get_date(x),
|
||||
'status': lambda x, y: x.strip(),
|
||||
'category': Category,
|
||||
'author': Author,
|
||||
'authors': lambda x, y: [Author(author.strip(), y) for author in x.split(',')],
|
||||
}
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Reader(object):
|
||||
class BaseReader(object):
|
||||
"""Base class to read files.
|
||||
|
||||
This class is used to process static files, and it can be inherited for
|
||||
other types of file. A Reader class must have the following attributes:
|
||||
|
||||
- enabled: (boolean) tell if the Reader class is enabled. It
|
||||
generally depends on the import of some dependency.
|
||||
- file_extensions: a list of file extensions that the Reader will process.
|
||||
- extensions: a list of extensions to use in the reader (typical use is
|
||||
Markdown).
|
||||
|
||||
"""
|
||||
enabled = True
|
||||
file_extensions = ['static']
|
||||
extensions = None
|
||||
|
||||
def __init__(self, settings):
|
||||
self.settings = settings
|
||||
|
||||
def process_metadata(self, name, value):
|
||||
if name in _METADATA_PROCESSORS:
|
||||
return _METADATA_PROCESSORS[name](value, self.settings)
|
||||
if name in METADATA_PROCESSORS:
|
||||
return METADATA_PROCESSORS[name](value, self.settings)
|
||||
return value
|
||||
|
||||
def read(self, source_path):
|
||||
"No-op parser"
|
||||
content = None
|
||||
metadata = {}
|
||||
return content, metadata
|
||||
|
||||
|
||||
class _FieldBodyTranslator(HTMLTranslator):
|
||||
|
||||
|
|
@ -74,11 +105,35 @@ class PelicanHTMLTranslator(HTMLTranslator):
|
|||
def depart_abbreviation(self, node):
|
||||
self.body.append('</abbr>')
|
||||
|
||||
def visit_image(self, node):
|
||||
# set an empty alt if alt is not specified
|
||||
# avoids that alt is taken from src
|
||||
node['alt'] = node.get('alt', '')
|
||||
return HTMLTranslator.visit_image(self, node)
|
||||
|
||||
|
||||
class RstReader(BaseReader):
|
||||
"""Reader for reStructuredText files"""
|
||||
|
||||
class RstReader(Reader):
|
||||
enabled = bool(docutils)
|
||||
file_extensions = ['rst']
|
||||
|
||||
class FileInput(docutils.io.FileInput):
|
||||
"""Patch docutils.io.FileInput to remove "U" mode in py3.
|
||||
|
||||
Universal newlines is enabled by default and "U" mode is deprecated
|
||||
in py3.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if six.PY3:
|
||||
kwargs['mode'] = kwargs.get('mode', 'r').replace('U', '')
|
||||
docutils.io.FileInput.__init__(self, *args, **kwargs)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RstReader, self).__init__(*args, **kwargs)
|
||||
|
||||
def _parse_metadata(self, document):
|
||||
"""Return the dict containing document metadata"""
|
||||
output = {}
|
||||
|
|
@ -91,6 +146,10 @@ class RstReader(Reader):
|
|||
value = render_node_to_html(document, body_elem)
|
||||
else:
|
||||
value = body_elem.astext()
|
||||
elif element.tagname == 'authors': # author list
|
||||
name = element.tagname
|
||||
value = [element.astext() for element in element.children]
|
||||
value = ','.join(value) # METADATA_PROCESSORS expects a string
|
||||
else: # standard fields (e.g. address)
|
||||
name = element.tagname
|
||||
value = element.astext()
|
||||
|
|
@ -99,20 +158,29 @@ class RstReader(Reader):
|
|||
output[name] = self.process_metadata(name, value)
|
||||
return output
|
||||
|
||||
def _get_publisher(self, filename):
|
||||
extra_params = {'initial_header_level': '2'}
|
||||
def _get_publisher(self, source_path):
|
||||
extra_params = {'initial_header_level': '2',
|
||||
'syntax_highlight': 'short',
|
||||
'input_encoding': 'utf-8',
|
||||
'exit_status_level': 2,
|
||||
'embed_stylesheet': False}
|
||||
user_params = self.settings.get('DOCUTILS_SETTINGS')
|
||||
if user_params:
|
||||
extra_params.update(user_params)
|
||||
|
||||
pub = docutils.core.Publisher(
|
||||
source_class=self.FileInput,
|
||||
destination_class=docutils.io.StringOutput)
|
||||
pub.set_components('standalone', 'restructuredtext', 'html')
|
||||
pub.writer.translator_class = PelicanHTMLTranslator
|
||||
pub.process_programmatic_settings(None, extra_params, None)
|
||||
pub.set_source(source_path=filename)
|
||||
pub.publish()
|
||||
pub.set_source(source_path=source_path)
|
||||
pub.publish(enable_exit_status=True)
|
||||
return pub
|
||||
|
||||
def read(self, filename):
|
||||
def read(self, source_path):
|
||||
"""Parses restructured text"""
|
||||
pub = self._get_publisher(filename)
|
||||
pub = self._get_publisher(source_path)
|
||||
parts = pub.writer.parts
|
||||
content = parts.get('body')
|
||||
|
||||
|
|
@ -122,76 +190,402 @@ class RstReader(Reader):
|
|||
return content, metadata
|
||||
|
||||
|
||||
class MarkdownReader(Reader):
|
||||
class MarkdownReader(BaseReader):
|
||||
"""Reader for Markdown files"""
|
||||
|
||||
enabled = bool(Markdown)
|
||||
file_extensions = ['md', 'markdown', 'mkd']
|
||||
extensions = ['codehilite', 'extra']
|
||||
file_extensions = ['md', 'markdown', 'mkd', 'mdown']
|
||||
|
||||
def read(self, filename):
|
||||
"""Parse content and metadata of markdown files"""
|
||||
markdown_extensions = self.settings.get('MARKDOWN_EXTENSIONS', [])
|
||||
if isinstance(markdown_extensions, (str, unicode)):
|
||||
markdown_extensions = [m.strip() for m in
|
||||
markdown_extensions.split(',')]
|
||||
text = pelican_open(filename)
|
||||
md = Markdown(extensions=set(
|
||||
self.extensions + markdown_extensions + ['meta']))
|
||||
content = md.convert(text)
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MarkdownReader, self).__init__(*args, **kwargs)
|
||||
self.extensions = list(self.settings['MD_EXTENSIONS'])
|
||||
if 'meta' not in self.extensions:
|
||||
self.extensions.append('meta')
|
||||
self._source_path = None
|
||||
|
||||
metadata = {}
|
||||
for name, value in md.Meta.items():
|
||||
def _parse_metadata(self, meta):
|
||||
"""Return the dict containing document metadata"""
|
||||
output = {}
|
||||
for name, value in meta.items():
|
||||
name = name.lower()
|
||||
metadata[name] = self.process_metadata(name, value[0])
|
||||
if name == "summary":
|
||||
# handle summary metadata as markdown
|
||||
# summary metadata is special case and join all list values
|
||||
summary_values = "\n".join(value)
|
||||
# reset the markdown instance to clear any state
|
||||
self._md.reset()
|
||||
summary = self._md.convert(summary_values)
|
||||
output[name] = self.process_metadata(name, summary)
|
||||
elif name in METADATA_PROCESSORS:
|
||||
if len(value) > 1:
|
||||
logger.warning('Duplicate definition of `%s` '
|
||||
'for %s. Using first one.', name, self._source_path)
|
||||
output[name] = self.process_metadata(name, value[0])
|
||||
elif len(value) > 1:
|
||||
# handle list metadata as list of string
|
||||
output[name] = self.process_metadata(name, value)
|
||||
else:
|
||||
# otherwise, handle metadata as single string
|
||||
output[name] = self.process_metadata(name, value[0])
|
||||
return output
|
||||
|
||||
def read(self, source_path):
|
||||
"""Parse content and metadata of markdown files"""
|
||||
|
||||
self._source_path = source_path
|
||||
self._md = Markdown(extensions=self.extensions)
|
||||
with pelican_open(source_path) as text:
|
||||
content = self._md.convert(text)
|
||||
|
||||
metadata = self._parse_metadata(self._md.Meta)
|
||||
return content, metadata
|
||||
|
||||
|
||||
class HtmlReader(Reader):
|
||||
file_extensions = ['html', 'htm']
|
||||
_re = re.compile('\<\!\-\-\#\s?[A-z0-9_-]*\s?\:s?[A-z0-9\s_-]*\s?\-\-\>')
|
||||
class HTMLReader(BaseReader):
|
||||
"""Parses HTML files as input, looking for meta, title, and body tags"""
|
||||
|
||||
file_extensions = ['htm', 'html']
|
||||
enabled = True
|
||||
|
||||
class _HTMLParser(HTMLParser):
|
||||
def __init__(self, settings, filename):
|
||||
try:
|
||||
# Python 3.4+
|
||||
HTMLParser.__init__(self, convert_charrefs=False)
|
||||
except TypeError:
|
||||
HTMLParser.__init__(self)
|
||||
self.body = ''
|
||||
self.metadata = {}
|
||||
self.settings = settings
|
||||
|
||||
self._data_buffer = ''
|
||||
|
||||
self._filename = filename
|
||||
|
||||
self._in_top_level = True
|
||||
self._in_head = False
|
||||
self._in_title = False
|
||||
self._in_body = False
|
||||
self._in_tags = False
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == 'head' and self._in_top_level:
|
||||
self._in_top_level = False
|
||||
self._in_head = True
|
||||
elif tag == 'title' and self._in_head:
|
||||
self._in_title = True
|
||||
self._data_buffer = ''
|
||||
elif tag == 'body' and self._in_top_level:
|
||||
self._in_top_level = False
|
||||
self._in_body = True
|
||||
self._data_buffer = ''
|
||||
elif tag == 'meta' and self._in_head:
|
||||
self._handle_meta_tag(attrs)
|
||||
|
||||
elif self._in_body:
|
||||
self._data_buffer += self.build_tag(tag, attrs, False)
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == 'head':
|
||||
if self._in_head:
|
||||
self._in_head = False
|
||||
self._in_top_level = True
|
||||
elif tag == 'title':
|
||||
self._in_title = False
|
||||
self.metadata['title'] = self._data_buffer
|
||||
elif tag == 'body':
|
||||
self.body = self._data_buffer
|
||||
self._in_body = False
|
||||
self._in_top_level = True
|
||||
elif self._in_body:
|
||||
self._data_buffer += '</{}>'.format(escape(tag))
|
||||
|
||||
def handle_startendtag(self, tag, attrs):
|
||||
if tag == 'meta' and self._in_head:
|
||||
self._handle_meta_tag(attrs)
|
||||
if self._in_body:
|
||||
self._data_buffer += self.build_tag(tag, attrs, True)
|
||||
|
||||
def handle_comment(self, data):
|
||||
self._data_buffer += '<!--{}-->'.format(data)
|
||||
|
||||
def handle_data(self, data):
|
||||
self._data_buffer += data
|
||||
|
||||
def handle_entityref(self, data):
|
||||
self._data_buffer += '&{};'.format(data)
|
||||
|
||||
def handle_charref(self, data):
|
||||
self._data_buffer += '&#{};'.format(data)
|
||||
|
||||
def build_tag(self, tag, attrs, close_tag):
|
||||
result = '<{}'.format(escape(tag))
|
||||
for k, v in attrs:
|
||||
result += ' ' + escape(k)
|
||||
if v is not None:
|
||||
result += '="{}"'.format(escape(v))
|
||||
if close_tag:
|
||||
return result + ' />'
|
||||
return result + '>'
|
||||
|
||||
def _handle_meta_tag(self, attrs):
|
||||
name = self._attr_value(attrs, 'name')
|
||||
if name is None:
|
||||
attr_serialized = ', '.join(['{}="{}"'.format(k, v) for k, v in attrs])
|
||||
logger.warning("Meta tag in file %s does not have a 'name' "
|
||||
"attribute, skipping. Attributes: %s",
|
||||
self._filename, attr_serialized)
|
||||
return
|
||||
name = name.lower()
|
||||
contents = self._attr_value(attrs, 'content', '')
|
||||
if not contents:
|
||||
contents = self._attr_value(attrs, 'contents', '')
|
||||
if contents:
|
||||
logger.warning(
|
||||
"Meta tag attribute 'contents' used in file %s, should"
|
||||
" be changed to 'content'",
|
||||
self._filename,
|
||||
extra={'limit_msg': ("Other files have meta tag "
|
||||
"attribute 'contents' that should "
|
||||
"be changed to 'content'")})
|
||||
|
||||
if name == 'keywords':
|
||||
name = 'tags'
|
||||
self.metadata[name] = contents
|
||||
|
||||
@classmethod
|
||||
def _attr_value(cls, attrs, name, default=None):
|
||||
return next((x[1] for x in attrs if x[0] == name), default)
|
||||
|
||||
def read(self, filename):
|
||||
"""Parse content and metadata of (x)HTML files"""
|
||||
"""Parse content and metadata of HTML files"""
|
||||
with pelican_open(filename) as content:
|
||||
metadata = {'title': 'unnamed'}
|
||||
for i in self._re.findall(content):
|
||||
key = i.split(':')[0][5:].strip()
|
||||
value = i.split(':')[-1][:-3].strip()
|
||||
name = key.lower()
|
||||
metadata[name] = self.process_metadata(name, value)
|
||||
parser = self._HTMLParser(self.settings, filename)
|
||||
parser.feed(content)
|
||||
parser.close()
|
||||
|
||||
return content, metadata
|
||||
metadata = {}
|
||||
for k in parser.metadata:
|
||||
metadata[k] = self.process_metadata(k, parser.metadata[k])
|
||||
return parser.body, metadata
|
||||
|
||||
|
||||
_EXTENSIONS = {}
|
||||
class Readers(FileStampDataCacher):
|
||||
"""Interface for all readers.
|
||||
|
||||
for cls in Reader.__subclasses__():
|
||||
for ext in cls.file_extensions:
|
||||
_EXTENSIONS[ext] = cls
|
||||
This class contains a mapping of file extensions / Reader classes, to know
|
||||
which Reader class must be used to read a file (based on its extension).
|
||||
This is customizable both with the 'READERS' setting, and with the
|
||||
'readers_init' signall for plugins.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, settings=None, cache_name=''):
|
||||
self.settings = settings or {}
|
||||
self.readers = {}
|
||||
self.reader_classes = {}
|
||||
|
||||
for cls in [BaseReader] + BaseReader.__subclasses__():
|
||||
if not cls.enabled:
|
||||
logger.debug('Missing dependencies for %s',
|
||||
', '.join(cls.file_extensions))
|
||||
continue
|
||||
|
||||
for ext in cls.file_extensions:
|
||||
self.reader_classes[ext] = cls
|
||||
|
||||
if self.settings['READERS']:
|
||||
self.reader_classes.update(self.settings['READERS'])
|
||||
|
||||
signals.readers_init.send(self)
|
||||
|
||||
for fmt, reader_class in self.reader_classes.items():
|
||||
if not reader_class:
|
||||
continue
|
||||
|
||||
self.readers[fmt] = reader_class(self.settings)
|
||||
|
||||
# set up caching
|
||||
cache_this_level = (cache_name != '' and
|
||||
self.settings['CONTENT_CACHING_LAYER'] == 'reader')
|
||||
caching_policy = cache_this_level and self.settings['CACHE_CONTENT']
|
||||
load_policy = cache_this_level and self.settings['LOAD_CONTENT_CACHE']
|
||||
super(Readers, self).__init__(settings, cache_name,
|
||||
caching_policy, load_policy,
|
||||
)
|
||||
|
||||
@property
|
||||
def extensions(self):
|
||||
return self.readers.keys()
|
||||
|
||||
def read_file(self, base_path, path, content_class=Page, fmt=None,
|
||||
context=None, preread_signal=None, preread_sender=None,
|
||||
context_signal=None, context_sender=None):
|
||||
"""Return a content object parsed with the given format."""
|
||||
|
||||
path = os.path.abspath(os.path.join(base_path, path))
|
||||
source_path = os.path.relpath(path, base_path)
|
||||
logger.debug('Read file %s -> %s',
|
||||
source_path, content_class.__name__)
|
||||
|
||||
if not fmt:
|
||||
_, ext = os.path.splitext(os.path.basename(path))
|
||||
fmt = ext[1:]
|
||||
|
||||
if fmt not in self.readers:
|
||||
raise TypeError(
|
||||
'Pelican does not know how to parse %s', path)
|
||||
|
||||
if preread_signal:
|
||||
logger.debug('Signal %s.send(%s)',
|
||||
preread_signal.name, preread_sender)
|
||||
preread_signal.send(preread_sender)
|
||||
|
||||
reader = self.readers[fmt]
|
||||
|
||||
metadata = default_metadata(
|
||||
settings=self.settings, process=reader.process_metadata)
|
||||
metadata.update(path_metadata(
|
||||
full_path=path, source_path=source_path,
|
||||
settings=self.settings))
|
||||
metadata.update(parse_path_metadata(
|
||||
source_path=source_path, settings=self.settings,
|
||||
process=reader.process_metadata))
|
||||
reader_name = reader.__class__.__name__
|
||||
metadata['reader'] = reader_name.replace('Reader', '').lower()
|
||||
|
||||
content, reader_metadata = self.get_cached_data(path, (None, None))
|
||||
if content is None:
|
||||
content, reader_metadata = reader.read(path)
|
||||
self.cache_data(path, (content, reader_metadata))
|
||||
metadata.update(reader_metadata)
|
||||
|
||||
if content:
|
||||
# find images with empty alt
|
||||
find_empty_alt(content, path)
|
||||
|
||||
# eventually filter the content with typogrify if asked so
|
||||
if self.settings['TYPOGRIFY']:
|
||||
from typogrify.filters import typogrify
|
||||
|
||||
def typogrify_wrapper(text):
|
||||
"""Ensures ignore_tags feature is backward compatible"""
|
||||
try:
|
||||
return typogrify(text, self.settings['TYPOGRIFY_IGNORE_TAGS'])
|
||||
except TypeError:
|
||||
return typogrify(text)
|
||||
|
||||
if content:
|
||||
content = typogrify_wrapper(content)
|
||||
metadata['title'] = typogrify_wrapper(metadata['title'])
|
||||
|
||||
if 'summary' in metadata:
|
||||
metadata['summary'] = typogrify_wrapper(metadata['summary'])
|
||||
|
||||
if context_signal:
|
||||
logger.debug('Signal %s.send(%s, <metadata>)',
|
||||
context_signal.name, context_sender)
|
||||
context_signal.send(context_sender, metadata=metadata)
|
||||
|
||||
return content_class(content=content, metadata=metadata,
|
||||
settings=self.settings, source_path=path,
|
||||
context=context)
|
||||
|
||||
|
||||
def read_file(filename, fmt=None, settings=None):
|
||||
"""Return a reader object using the given format."""
|
||||
if not fmt:
|
||||
fmt = filename.split('.')[-1]
|
||||
def find_empty_alt(content, path):
|
||||
"""Find images with empty alt
|
||||
|
||||
if fmt not in _EXTENSIONS:
|
||||
raise TypeError('Pelican does not know how to parse %s' % filename)
|
||||
Create warnings for all images with empty alt (up to a certain number),
|
||||
as they are really likely to be accessibility flaws.
|
||||
|
||||
reader = _EXTENSIONS[fmt](settings)
|
||||
settings_key = '%s_EXTENSIONS' % fmt.upper()
|
||||
"""
|
||||
imgs = re.compile(r"""
|
||||
(?:
|
||||
# src before alt
|
||||
<img
|
||||
[^\>]*
|
||||
src=(['"])(.*)\1
|
||||
[^\>]*
|
||||
alt=(['"])\3
|
||||
)|(?:
|
||||
# alt before src
|
||||
<img
|
||||
[^\>]*
|
||||
alt=(['"])\4
|
||||
[^\>]*
|
||||
src=(['"])(.*)\5
|
||||
)
|
||||
""", re.X)
|
||||
for match in re.findall(imgs, content):
|
||||
logger.warning(
|
||||
'Empty alt attribute for image %s in %s',
|
||||
os.path.basename(match[1] + match[5]), path,
|
||||
extra={'limit_msg': 'Other images have empty alt attributes'})
|
||||
|
||||
if settings and settings_key in settings:
|
||||
reader.extensions = settings[settings_key]
|
||||
|
||||
if not reader.enabled:
|
||||
raise ValueError("Missing dependencies for %s" % fmt)
|
||||
def default_metadata(settings=None, process=None):
|
||||
metadata = {}
|
||||
if settings:
|
||||
if 'DEFAULT_CATEGORY' in settings:
|
||||
value = settings['DEFAULT_CATEGORY']
|
||||
if process:
|
||||
value = process('category', value)
|
||||
metadata['category'] = value
|
||||
if settings.get('DEFAULT_DATE', None) and settings['DEFAULT_DATE'] != 'fs':
|
||||
metadata['date'] = SafeDatetime(*settings['DEFAULT_DATE'])
|
||||
return metadata
|
||||
|
||||
content, metadata = reader.read(filename)
|
||||
|
||||
# eventually filter the content with typogrify if asked so
|
||||
if settings and settings['TYPOGRIFY']:
|
||||
from typogrify.filters import typogrify
|
||||
content = typogrify(content)
|
||||
metadata['title'] = typogrify(metadata['title'])
|
||||
def path_metadata(full_path, source_path, settings=None):
|
||||
metadata = {}
|
||||
if settings:
|
||||
if settings.get('DEFAULT_DATE', None) == 'fs':
|
||||
metadata['date'] = SafeDatetime.fromtimestamp(
|
||||
os.stat(full_path).st_ctime)
|
||||
metadata.update(settings.get('EXTRA_PATH_METADATA', {}).get(
|
||||
source_path, {}))
|
||||
return metadata
|
||||
|
||||
return content, metadata
|
||||
|
||||
def parse_path_metadata(source_path, settings=None, process=None):
|
||||
"""Extract a metadata dictionary from a file's path
|
||||
|
||||
>>> import pprint
|
||||
>>> settings = {
|
||||
... 'FILENAME_METADATA': '(?P<slug>[^.]*).*',
|
||||
... 'PATH_METADATA':
|
||||
... '(?P<category>[^/]*)/(?P<date>\d{4}-\d{2}-\d{2})/.*',
|
||||
... }
|
||||
>>> reader = BaseReader(settings=settings)
|
||||
>>> metadata = parse_path_metadata(
|
||||
... source_path='my-cat/2013-01-01/my-slug.html',
|
||||
... settings=settings,
|
||||
... process=reader.process_metadata)
|
||||
>>> pprint.pprint(metadata) # doctest: +ELLIPSIS
|
||||
{'category': <pelican.urlwrappers.Category object at ...>,
|
||||
'date': SafeDatetime(2013, 1, 1, 0, 0),
|
||||
'slug': 'my-slug'}
|
||||
"""
|
||||
metadata = {}
|
||||
dirname, basename = os.path.split(source_path)
|
||||
base, ext = os.path.splitext(basename)
|
||||
subdir = os.path.basename(dirname)
|
||||
if settings:
|
||||
checks = []
|
||||
for key, data in [('FILENAME_METADATA', base),
|
||||
('PATH_METADATA', source_path)]:
|
||||
checks.append((settings.get(key, None), data))
|
||||
if settings.get('USE_FOLDER_AS_CATEGORY', None):
|
||||
checks.insert(0, ('(?P<category>.*)', subdir))
|
||||
for regexp, data in checks:
|
||||
if regexp and data:
|
||||
match = re.match(regexp, data)
|
||||
if match:
|
||||
# .items() for py3k compat.
|
||||
for k, v in match.groupdict().items():
|
||||
if k not in metadata:
|
||||
k = k.lower() # metadata must be lowercase
|
||||
if process:
|
||||
v = process(k, v)
|
||||
metadata[k] = v
|
||||
return metadata
|
||||
|
|
|
|||
|
|
@ -1,25 +1,38 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
from docutils import nodes, utils
|
||||
from docutils.parsers.rst import directives, roles, Directive
|
||||
from pygments.formatters import HtmlFormatter
|
||||
from pygments import highlight
|
||||
from pygments.lexers import get_lexer_by_name, TextLexer
|
||||
import re
|
||||
|
||||
INLINESTYLES = False
|
||||
DEFAULT = HtmlFormatter(noclasses=INLINESTYLES)
|
||||
VARIANTS = {
|
||||
'linenos': HtmlFormatter(noclasses=INLINESTYLES, linenos=True),
|
||||
}
|
||||
import six
|
||||
import pelican.settings as pys
|
||||
|
||||
|
||||
class Pygments(Directive):
|
||||
""" Source code syntax hightlighting.
|
||||
""" Source code syntax highlighting.
|
||||
"""
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
option_spec = dict([(key, directives.flag) for key in VARIANTS])
|
||||
option_spec = {
|
||||
'anchorlinenos': directives.flag,
|
||||
'classprefix': directives.unchanged,
|
||||
'hl_lines': directives.unchanged,
|
||||
'lineanchors': directives.unchanged,
|
||||
'linenos': directives.unchanged,
|
||||
'linenospecial': directives.nonnegative_int,
|
||||
'linenostart': directives.nonnegative_int,
|
||||
'linenostep': directives.nonnegative_int,
|
||||
'lineseparator': directives.unchanged,
|
||||
'linespans': directives.unchanged,
|
||||
'nobackground': directives.flag,
|
||||
'nowrap': directives.flag,
|
||||
'tagsfile': directives.unchanged,
|
||||
'tagurlformat': directives.unchanged,
|
||||
}
|
||||
has_content = True
|
||||
|
||||
def run(self):
|
||||
|
|
@ -29,84 +42,48 @@ class Pygments(Directive):
|
|||
except ValueError:
|
||||
# no lexer found - use the text one instead of an exception
|
||||
lexer = TextLexer()
|
||||
# take an arbitrary option if more than one is given
|
||||
formatter = self.options and VARIANTS[self.options.keys()[0]] \
|
||||
or DEFAULT
|
||||
parsed = highlight(u'\n'.join(self.content), lexer, formatter)
|
||||
|
||||
# Fetch the defaults
|
||||
if pys.PYGMENTS_RST_OPTIONS is not None:
|
||||
for k, v in six.iteritems(pys.PYGMENTS_RST_OPTIONS):
|
||||
# Locally set options overrides the defaults
|
||||
if k not in self.options:
|
||||
self.options[k] = v
|
||||
|
||||
if ('linenos' in self.options and
|
||||
self.options['linenos'] not in ('table', 'inline')):
|
||||
if self.options['linenos'] == 'none':
|
||||
self.options.pop('linenos')
|
||||
else:
|
||||
self.options['linenos'] = 'table'
|
||||
|
||||
for flag in ('nowrap', 'nobackground', 'anchorlinenos'):
|
||||
if flag in self.options:
|
||||
self.options[flag] = True
|
||||
|
||||
# noclasses should already default to False, but just in case...
|
||||
formatter = HtmlFormatter(noclasses=False, **self.options)
|
||||
parsed = highlight('\n'.join(self.content), lexer, formatter)
|
||||
return [nodes.raw('', parsed, format='html')]
|
||||
|
||||
directives.register_directive('code-block', Pygments)
|
||||
directives.register_directive('sourcecode', Pygments)
|
||||
|
||||
|
||||
class YouTube(Directive):
|
||||
""" Embed YouTube video in posts.
|
||||
|
||||
Courtesy of Brian Hsu: https://gist.github.com/1422773
|
||||
|
||||
VIDEO_ID is required, with / height are optional integer,
|
||||
and align could be left / center / right.
|
||||
|
||||
Usage:
|
||||
.. youtube:: VIDEO_ID
|
||||
:width: 640
|
||||
:height: 480
|
||||
:align: center
|
||||
"""
|
||||
|
||||
def align(argument):
|
||||
"""Conversion function for the "align" option."""
|
||||
return directives.choice(argument, ('left', 'center', 'right'))
|
||||
|
||||
required_arguments = 1
|
||||
optional_arguments = 2
|
||||
option_spec = {
|
||||
'width': directives.positive_int,
|
||||
'height': directives.positive_int,
|
||||
'align': align
|
||||
}
|
||||
|
||||
final_argument_whitespace = False
|
||||
has_content = False
|
||||
|
||||
def run(self):
|
||||
videoID = self.arguments[0].strip()
|
||||
width = 420
|
||||
height = 315
|
||||
align = 'left'
|
||||
|
||||
if 'width' in self.options:
|
||||
width = self.options['width']
|
||||
|
||||
if 'height' in self.options:
|
||||
height = self.options['height']
|
||||
|
||||
if 'align' in self.options:
|
||||
align = self.options['align']
|
||||
|
||||
url = 'http://www.youtube.com/embed/%s' % videoID
|
||||
div_block = '<div class="youtube" align="%s">' % align
|
||||
embed_block = '<iframe width="%s" height="%s" src="%s" '\
|
||||
'frameborder="0"></iframe>' % (width, height, url)
|
||||
|
||||
return [
|
||||
nodes.raw('', div_block, format='html'),
|
||||
nodes.raw('', embed_block, format='html'),
|
||||
nodes.raw('', '</div>', format='html')]
|
||||
|
||||
directives.register_directive('youtube', YouTube)
|
||||
|
||||
_abbr_re = re.compile('\((.*)\)$')
|
||||
|
||||
class abbreviation(nodes.Inline, nodes.TextElement): pass
|
||||
|
||||
class abbreviation(nodes.Inline, nodes.TextElement):
|
||||
pass
|
||||
|
||||
|
||||
def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
text = utils.unescape(text)
|
||||
m = _abbr_re.search(text)
|
||||
if m is None:
|
||||
return [abbreviation(text, text)], []
|
||||
abbr = text[:m.start()].strip()
|
||||
expl = m.group(1)
|
||||
return [abbreviation(abbr, abbr, explanation=expl)], []
|
||||
text = utils.unescape(text)
|
||||
m = _abbr_re.search(text)
|
||||
if m is None:
|
||||
return [abbreviation(text, text)], []
|
||||
abbr = text[:m.start()].strip()
|
||||
expl = m.group(1)
|
||||
return [abbreviation(abbr, abbr, explanation=expl)], []
|
||||
|
||||
roles.register_local_role('abbr', abbr_role)
|
||||
|
|
|
|||
52
pelican/server.py
Normal file
52
pelican/server.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
from __future__ import print_function
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
try:
|
||||
import SimpleHTTPServer as srvmod
|
||||
except ImportError:
|
||||
import http.server as srvmod # NOQA
|
||||
|
||||
try:
|
||||
import SocketServer as socketserver
|
||||
except ImportError:
|
||||
import socketserver # NOQA
|
||||
|
||||
PORT = len(sys.argv) == 2 and int(sys.argv[1]) or 8000
|
||||
SUFFIXES = ['', '.html', '/index.html']
|
||||
|
||||
|
||||
class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
# we are trying to detect the file by having a fallback mechanism
|
||||
found = False
|
||||
for suffix in 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)
|
||||
found = True
|
||||
break
|
||||
logging.info("Tried to find file %s, but it doesn't exist. ", self.path)
|
||||
if not found:
|
||||
logging.warning("Unable to find file %s or variations.", self.path)
|
||||
|
||||
Handler = ComplexHTTPRequestHandler
|
||||
|
||||
socketserver.TCPServer.allow_reuse_address = True
|
||||
try:
|
||||
httpd = socketserver.TCPServer(("", PORT), Handler)
|
||||
except OSError as e:
|
||||
logging.error("Could not listen on port %s", PORT)
|
||||
sys.exit(getattr(e, 'exitcode', 1))
|
||||
|
||||
|
||||
logging.info("Serving at port %s", PORT)
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt as e:
|
||||
logging.info("Shutting down server")
|
||||
httpd.socket.close()
|
||||
|
|
@ -1,188 +1,381 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals, print_function
|
||||
import six
|
||||
|
||||
import copy
|
||||
import imp
|
||||
import inspect
|
||||
import os
|
||||
import locale
|
||||
import logging
|
||||
|
||||
try:
|
||||
# SourceFileLoader is the recommended way in 3.3+
|
||||
from importlib.machinery import SourceFileLoader
|
||||
load_source = lambda name, path: SourceFileLoader(name, path).load_module()
|
||||
except ImportError:
|
||||
# but it does not exist in 3.2-, so fall back to imp
|
||||
import imp
|
||||
load_source = imp.load_source
|
||||
|
||||
from os.path import isabs
|
||||
|
||||
from pelican.log import LimitFilter
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
DEFAULT_THEME = os.sep.join([os.path.dirname(os.path.abspath(__file__)),
|
||||
"themes/notmyidea"])
|
||||
_DEFAULT_CONFIG = {'PATH': '.',
|
||||
'ARTICLE_DIR': '',
|
||||
'ARTICLE_EXCLUDES': ('pages',),
|
||||
'PAGE_DIR': 'pages',
|
||||
'PAGE_EXCLUDES': (),
|
||||
'THEME': DEFAULT_THEME,
|
||||
'OUTPUT_PATH': 'output/',
|
||||
'MARKUP': ('rst', 'md'),
|
||||
'STATIC_PATHS': ['images', ],
|
||||
'THEME_STATIC_PATHS': ['static', ],
|
||||
'FEED_ATOM': 'feeds/all.atom.xml',
|
||||
'CATEGORY_FEED_ATOM': 'feeds/%s.atom.xml',
|
||||
'TRANSLATION_FEED': 'feeds/all-%s.atom.xml',
|
||||
'FEED_MAX_ITEMS': '',
|
||||
'SITEURL': '',
|
||||
'SITENAME': 'A Pelican Blog',
|
||||
'DISPLAY_PAGES_ON_MENU': True,
|
||||
'PDF_GENERATOR': False,
|
||||
'OUTPUT_SOURCES': False,
|
||||
'OUTPUT_SOURCES_EXTENSION': '.text',
|
||||
'DEFAULT_CATEGORY': 'misc',
|
||||
'DEFAULT_DATE': 'fs',
|
||||
'WITH_FUTURE_DATES': True,
|
||||
'CSS_FILE': 'main.css',
|
||||
'NEWEST_FIRST_ARCHIVES': True,
|
||||
'REVERSE_CATEGORY_ORDER': False,
|
||||
'DELETE_OUTPUT_DIRECTORY': False,
|
||||
'ARTICLE_URL': '{slug}.html',
|
||||
'ARTICLE_SAVE_AS': '{slug}.html',
|
||||
'ARTICLE_LANG_URL': '{slug}-{lang}.html',
|
||||
'ARTICLE_LANG_SAVE_AS': '{slug}-{lang}.html',
|
||||
'PAGE_URL': 'pages/{slug}.html',
|
||||
'PAGE_SAVE_AS': 'pages/{slug}.html',
|
||||
'PAGE_LANG_URL': 'pages/{slug}-{lang}.html',
|
||||
'PAGE_LANG_SAVE_AS': 'pages/{slug}-{lang}.html',
|
||||
'CATEGORY_URL': 'category/{slug}.html',
|
||||
'CATEGORY_SAVE_AS': 'category/{slug}.html',
|
||||
'TAG_URL': 'tag/{slug}.html',
|
||||
'TAG_SAVE_AS': 'tag/{slug}.html',
|
||||
'AUTHOR_URL': u'author/{slug}.html',
|
||||
'AUTHOR_SAVE_AS': u'author/{slug}.html',
|
||||
'RELATIVE_URLS': True,
|
||||
'DEFAULT_LANG': 'en',
|
||||
'TAG_CLOUD_STEPS': 4,
|
||||
'TAG_CLOUD_MAX_ITEMS': 100,
|
||||
'DIRECT_TEMPLATES': ('index', 'tags', 'categories', 'archives'),
|
||||
'EXTRA_TEMPLATES_PATHS' : [],
|
||||
'PAGINATED_DIRECT_TEMPLATES': ('index', ),
|
||||
'PELICAN_CLASS': 'pelican.Pelican',
|
||||
'DEFAULT_DATE_FORMAT': '%a %d %B %Y',
|
||||
'DATE_FORMATS': {},
|
||||
'JINJA_EXTENSIONS': [],
|
||||
'LOCALE': '', # default to user locale
|
||||
'DEFAULT_PAGINATION': False,
|
||||
'DEFAULT_ORPHANS': 0,
|
||||
'DEFAULT_METADATA': (),
|
||||
'FILES_TO_COPY': (),
|
||||
'DEFAULT_STATUS': 'published',
|
||||
'ARTICLE_PERMALINK_STRUCTURE': '',
|
||||
'TYPOGRIFY': False,
|
||||
'LESS_GENERATOR': False,
|
||||
'SUMMARY_MAX_LENGTH': 50,
|
||||
'WEBASSETS': False,
|
||||
'PLUGINS': [],
|
||||
'MARKDOWN_EXTENSIONS': ['toc', ],
|
||||
}
|
||||
DEFAULT_THEME = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
'themes', 'notmyidea')
|
||||
DEFAULT_CONFIG = {
|
||||
'PATH': os.curdir,
|
||||
'ARTICLE_PATHS': [''],
|
||||
'ARTICLE_EXCLUDES': [],
|
||||
'PAGE_PATHS': ['pages'],
|
||||
'PAGE_EXCLUDES': [],
|
||||
'THEME': DEFAULT_THEME,
|
||||
'OUTPUT_PATH': 'output',
|
||||
'READERS': {},
|
||||
'STATIC_PATHS': ['images'],
|
||||
'STATIC_EXCLUDES': [],
|
||||
'STATIC_EXCLUDE_SOURCES': True,
|
||||
'THEME_STATIC_DIR': 'theme',
|
||||
'THEME_STATIC_PATHS': ['static', ],
|
||||
'FEED_ALL_ATOM': os.path.join('feeds', 'all.atom.xml'),
|
||||
'CATEGORY_FEED_ATOM': os.path.join('feeds', '%s.atom.xml'),
|
||||
'AUTHOR_FEED_ATOM': os.path.join('feeds', '%s.atom.xml'),
|
||||
'AUTHOR_FEED_RSS': os.path.join('feeds', '%s.rss.xml'),
|
||||
'TRANSLATION_FEED_ATOM': os.path.join('feeds', 'all-%s.atom.xml'),
|
||||
'FEED_MAX_ITEMS': '',
|
||||
'SITEURL': '',
|
||||
'SITENAME': 'A Pelican Blog',
|
||||
'DISPLAY_PAGES_ON_MENU': True,
|
||||
'DISPLAY_CATEGORIES_ON_MENU': True,
|
||||
'DOCUTILS_SETTINGS': {},
|
||||
'OUTPUT_SOURCES': False,
|
||||
'OUTPUT_SOURCES_EXTENSION': '.text',
|
||||
'USE_FOLDER_AS_CATEGORY': True,
|
||||
'DEFAULT_CATEGORY': 'misc',
|
||||
'WITH_FUTURE_DATES': True,
|
||||
'CSS_FILE': 'main.css',
|
||||
'NEWEST_FIRST_ARCHIVES': True,
|
||||
'REVERSE_CATEGORY_ORDER': False,
|
||||
'DELETE_OUTPUT_DIRECTORY': False,
|
||||
'OUTPUT_RETENTION': (),
|
||||
'ARTICLE_URL': '{slug}.html',
|
||||
'ARTICLE_SAVE_AS': '{slug}.html',
|
||||
'ARTICLE_ORDER_BY': 'slug',
|
||||
'ARTICLE_LANG_URL': '{slug}-{lang}.html',
|
||||
'ARTICLE_LANG_SAVE_AS': '{slug}-{lang}.html',
|
||||
'DRAFT_URL': 'drafts/{slug}.html',
|
||||
'DRAFT_SAVE_AS': os.path.join('drafts', '{slug}.html'),
|
||||
'DRAFT_LANG_URL': 'drafts/{slug}-{lang}.html',
|
||||
'DRAFT_LANG_SAVE_AS': os.path.join('drafts', '{slug}-{lang}.html'),
|
||||
'PAGE_URL': 'pages/{slug}.html',
|
||||
'PAGE_SAVE_AS': os.path.join('pages', '{slug}.html'),
|
||||
'PAGE_ORDER_BY': 'basename',
|
||||
'PAGE_LANG_URL': 'pages/{slug}-{lang}.html',
|
||||
'PAGE_LANG_SAVE_AS': os.path.join('pages', '{slug}-{lang}.html'),
|
||||
'STATIC_URL': '{path}',
|
||||
'STATIC_SAVE_AS': '{path}',
|
||||
'PDF_GENERATOR': False,
|
||||
'PDF_STYLE_PATH': '',
|
||||
'PDF_STYLE': 'twelvepoint',
|
||||
'CATEGORY_URL': 'category/{slug}.html',
|
||||
'CATEGORY_SAVE_AS': os.path.join('category', '{slug}.html'),
|
||||
'TAG_URL': 'tag/{slug}.html',
|
||||
'TAG_SAVE_AS': os.path.join('tag', '{slug}.html'),
|
||||
'AUTHOR_URL': 'author/{slug}.html',
|
||||
'AUTHOR_SAVE_AS': os.path.join('author', '{slug}.html'),
|
||||
'PAGINATION_PATTERNS': [
|
||||
(0, '{name}{number}{extension}', '{name}{number}{extension}'),
|
||||
],
|
||||
'YEAR_ARCHIVE_SAVE_AS': '',
|
||||
'MONTH_ARCHIVE_SAVE_AS': '',
|
||||
'DAY_ARCHIVE_SAVE_AS': '',
|
||||
'RELATIVE_URLS': False,
|
||||
'DEFAULT_LANG': 'en',
|
||||
'TAG_CLOUD_STEPS': 4,
|
||||
'TAG_CLOUD_MAX_ITEMS': 100,
|
||||
'DIRECT_TEMPLATES': ('index', 'tags', 'categories', 'authors', 'archives'),
|
||||
'EXTRA_TEMPLATES_PATHS': [],
|
||||
'PAGINATED_DIRECT_TEMPLATES': ('index', ),
|
||||
'PELICAN_CLASS': 'pelican.Pelican',
|
||||
'DEFAULT_DATE_FORMAT': '%a %d %B %Y',
|
||||
'DATE_FORMATS': {},
|
||||
'MD_EXTENSIONS': ['codehilite(css_class=highlight)', 'extra'],
|
||||
'JINJA_EXTENSIONS': [],
|
||||
'JINJA_FILTERS': {},
|
||||
'LOG_FILTER': [],
|
||||
'LOCALE': [''], # defaults to user locale
|
||||
'DEFAULT_PAGINATION': False,
|
||||
'DEFAULT_ORPHANS': 0,
|
||||
'DEFAULT_METADATA': (),
|
||||
'FILENAME_METADATA': '(?P<date>\d{4}-\d{2}-\d{2}).*',
|
||||
'PATH_METADATA': '',
|
||||
'EXTRA_PATH_METADATA': {},
|
||||
'DEFAULT_STATUS': 'published',
|
||||
'ARTICLE_PERMALINK_STRUCTURE': '',
|
||||
'TYPOGRIFY': False,
|
||||
'TYPOGRIFY_IGNORE_TAGS': [],
|
||||
'SUMMARY_MAX_LENGTH': 50,
|
||||
'PLUGIN_PATHS': [],
|
||||
'PLUGINS': [],
|
||||
'PYGMENTS_RST_OPTIONS': {},
|
||||
'TEMPLATE_PAGES': {},
|
||||
'IGNORE_FILES': ['.#*'],
|
||||
'SLUG_SUBSTITUTIONS': (),
|
||||
'INTRASITE_LINK_REGEX': '[{|](?P<what>.*?)[|}]',
|
||||
'SLUGIFY_SOURCE': 'title',
|
||||
'CACHE_CONTENT': True,
|
||||
'CONTENT_CACHING_LAYER': 'reader',
|
||||
'CACHE_PATH': 'cache',
|
||||
'GZIP_CACHE': True,
|
||||
'CHECK_MODIFIED_METHOD': 'mtime',
|
||||
'LOAD_CONTENT_CACHE': True,
|
||||
'AUTORELOAD_IGNORE_CACHE': False,
|
||||
'WRITE_SELECTED': [],
|
||||
}
|
||||
|
||||
PYGMENTS_RST_OPTIONS = None
|
||||
|
||||
|
||||
def read_settings(filename=None):
|
||||
if filename:
|
||||
local_settings = get_settings_from_file(filename)
|
||||
def read_settings(path=None, override=None):
|
||||
if path:
|
||||
local_settings = get_settings_from_file(path)
|
||||
# Make the paths relative to the settings file
|
||||
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 p not in ('THEME') or os.path.exists(absp):
|
||||
local_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 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:
|
||||
local_settings['PLUGIN_PATHS'] = [os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(path), pluginpath)))
|
||||
if not isabs(pluginpath) else pluginpath for pluginpath in local_settings['PLUGIN_PATHS']]
|
||||
else:
|
||||
local_settings = copy.deepcopy(_DEFAULT_CONFIG)
|
||||
configured_settings = configure_settings(local_settings, None, filename)
|
||||
return configured_settings
|
||||
local_settings = copy.deepcopy(DEFAULT_CONFIG)
|
||||
|
||||
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
|
||||
|
||||
|
||||
def get_settings_from_module(module=None, default_settings=_DEFAULT_CONFIG):
|
||||
"""
|
||||
Load settings from a module, returning a dict.
|
||||
"""
|
||||
def get_settings_from_module(module=None, default_settings=DEFAULT_CONFIG):
|
||||
"""Loads settings from a module, returns a dictionary."""
|
||||
|
||||
context = copy.deepcopy(default_settings)
|
||||
if module is not None:
|
||||
context.update(
|
||||
(k, v) for k, v in inspect.getmembers(module) if k.isupper()
|
||||
)
|
||||
context.update(
|
||||
(k, v) for k, v in inspect.getmembers(module) if k.isupper())
|
||||
return context
|
||||
|
||||
|
||||
def get_settings_from_file(filename, default_settings=_DEFAULT_CONFIG):
|
||||
"""
|
||||
Load settings from a file path, returning a dict.
|
||||
def get_settings_from_file(path, default_settings=DEFAULT_CONFIG):
|
||||
"""Loads settings from a file path, returning a dict."""
|
||||
|
||||
"""
|
||||
|
||||
name = os.path.basename(filename).rpartition(".")[0]
|
||||
module = imp.load_source(name, filename)
|
||||
name, ext = os.path.splitext(os.path.basename(path))
|
||||
module = load_source(name, path)
|
||||
return get_settings_from_module(module, default_settings=default_settings)
|
||||
|
||||
|
||||
def configure_settings(settings, default_settings=None, filename=None):
|
||||
"""Provide optimizations, error checking, and warnings for loaded settings"""
|
||||
if default_settings is None:
|
||||
default_settings = copy.deepcopy(_DEFAULT_CONFIG)
|
||||
def configure_settings(settings):
|
||||
"""Provide optimizations, error checking, and warnings for the given
|
||||
settings.
|
||||
Also, specify the log messages to be ignored.
|
||||
"""
|
||||
if not 'PATH' in settings or not os.path.isdir(settings['PATH']):
|
||||
raise Exception('You need to specify a path containing the content'
|
||||
' (see pelican --help for more information)')
|
||||
|
||||
# Make the paths relative to the settings file
|
||||
if filename:
|
||||
for path in ['PATH', 'OUTPUT_PATH']:
|
||||
if path in settings:
|
||||
if settings[path] is not None and not isabs(settings[path]):
|
||||
settings[path] = os.path.abspath(os.path.normpath(
|
||||
os.path.join(os.path.dirname(filename), settings[path]))
|
||||
)
|
||||
# specify the log messages to be ignored
|
||||
LimitFilter._ignore.update(set(settings.get('LOG_FILTER',
|
||||
DEFAULT_CONFIG['LOG_FILTER'])))
|
||||
|
||||
# if locales is not a list, make it one
|
||||
locales = settings['LOCALE']
|
||||
# lookup the theme in "pelican/themes" if the given one doesn't exist
|
||||
if not os.path.isdir(settings['THEME']):
|
||||
theme_path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
'themes',
|
||||
settings['THEME'])
|
||||
if os.path.exists(theme_path):
|
||||
settings['THEME'] = theme_path
|
||||
else:
|
||||
raise Exception("Could not find the theme %s"
|
||||
% settings['THEME'])
|
||||
|
||||
if isinstance(locales, basestring):
|
||||
locales = [locales]
|
||||
# make paths selected for writing absolute if necessary
|
||||
settings['WRITE_SELECTED'] = [
|
||||
os.path.abspath(path) for path in
|
||||
settings.get('WRITE_SELECTED', DEFAULT_CONFIG['WRITE_SELECTED'])
|
||||
]
|
||||
|
||||
# standardize strings to lowercase strings
|
||||
for key in [
|
||||
'DEFAULT_LANG',
|
||||
]:
|
||||
if key in settings:
|
||||
settings[key] = settings[key].lower()
|
||||
|
||||
# standardize strings to lists
|
||||
for key in [
|
||||
'LOCALE',
|
||||
]:
|
||||
if key in settings and isinstance(settings[key], six.string_types):
|
||||
settings[key] = [settings[key]]
|
||||
|
||||
# check settings that must be a particular type
|
||||
for key, types in [
|
||||
('OUTPUT_SOURCES_EXTENSION', six.string_types),
|
||||
('FILENAME_METADATA', six.string_types),
|
||||
]:
|
||||
if key in settings and not isinstance(settings[key], types):
|
||||
value = settings.pop(key)
|
||||
logger.warn('Detected misconfigured %s (%s), '
|
||||
'falling back to the default (%s)',
|
||||
key, value, DEFAULT_CONFIG[key])
|
||||
|
||||
# try to set the different locales, fallback on the default.
|
||||
if not locales:
|
||||
locales = _DEFAULT_CONFIG['LOCALE']
|
||||
locales = settings.get('LOCALE', DEFAULT_CONFIG['LOCALE'])
|
||||
|
||||
for locale_ in locales:
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, locale_)
|
||||
locale.setlocale(locale.LC_ALL, str(locale_))
|
||||
break # break if it is successful
|
||||
except locale.Error:
|
||||
pass
|
||||
else:
|
||||
logger.warn("LOCALE option doesn't contain a correct value")
|
||||
logger.warning("LOCALE option doesn't contain a correct value")
|
||||
|
||||
if ('SITEURL' in settings):
|
||||
# If SITEURL has a trailing slash, remove it and provide a warning
|
||||
siteurl = settings['SITEURL']
|
||||
if (siteurl.endswith('/')):
|
||||
settings['SITEURL'] = siteurl[:-1]
|
||||
logger.warn("Removed extraneous trailing slash from SITEURL.")
|
||||
# If SITEURL is defined but FEED_DOMAIN isn't, set FEED_DOMAIN = SITEURL
|
||||
logger.warning("Removed extraneous trailing slash from SITEURL.")
|
||||
# If SITEURL is defined but FEED_DOMAIN isn't,
|
||||
# set FEED_DOMAIN to SITEURL
|
||||
if not 'FEED_DOMAIN' in settings:
|
||||
settings['FEED_DOMAIN'] = settings['SITEURL']
|
||||
|
||||
# check content caching layer and warn of incompatibilities
|
||||
if (settings.get('CACHE_CONTENT', False) and
|
||||
settings.get('CONTENT_CACHING_LAYER', '') == 'generator' and
|
||||
settings.get('WITH_FUTURE_DATES', DEFAULT_CONFIG['WITH_FUTURE_DATES'])):
|
||||
logger.warning('WITH_FUTURE_DATES conflicts with '
|
||||
"CONTENT_CACHING_LAYER set to 'generator', "
|
||||
"use 'reader' layer instead")
|
||||
|
||||
# Warn if feeds are generated with both SITEURL & FEED_DOMAIN undefined
|
||||
if (('FEED_ATOM' in settings) or ('FEED_RSS' in settings)) and (not 'FEED_DOMAIN' in settings):
|
||||
logger.warn("Since feed URLs should always be absolute, you should specify "
|
||||
"FEED_DOMAIN in your settings. (e.g., 'FEED_DOMAIN = "
|
||||
"http://www.example.com')")
|
||||
feed_keys = [
|
||||
'FEED_ATOM', 'FEED_RSS',
|
||||
'FEED_ALL_ATOM', 'FEED_ALL_RSS',
|
||||
'CATEGORY_FEED_ATOM', 'CATEGORY_FEED_RSS',
|
||||
'AUTHOR_FEED_ATOM', 'AUTHOR_FEED_RSS',
|
||||
'TAG_FEED_ATOM', 'TAG_FEED_RSS',
|
||||
'TRANSLATION_FEED_ATOM', 'TRANSLATION_FEED_RSS',
|
||||
]
|
||||
|
||||
if any(settings.get(k) for k in feed_keys):
|
||||
if not settings.get('SITEURL'):
|
||||
logger.warning('Feeds generated without SITEURL set properly may'
|
||||
' not be valid')
|
||||
|
||||
if not 'TIMEZONE' in settings:
|
||||
logger.warn("No timezone information specified in the settings. Assuming"
|
||||
" your timezone is UTC for feed generation. Check "
|
||||
"http://docs.notmyidea.org/alexis/pelican/settings.html#timezone "
|
||||
"for more information")
|
||||
logger.warning(
|
||||
'No timezone information specified in the settings. Assuming'
|
||||
' your timezone is UTC for feed generation. Check '
|
||||
'http://docs.getpelican.com/en/latest/settings.html#timezone '
|
||||
'for more information')
|
||||
|
||||
if 'WEBASSETS' in settings and settings['WEBASSETS'] is not False:
|
||||
# fix up pagination rules
|
||||
from pelican.paginator import PaginationRule
|
||||
pagination_rules = [
|
||||
PaginationRule(*r) for r in settings.get(
|
||||
'PAGINATION_PATTERNS',
|
||||
DEFAULT_CONFIG['PAGINATION_PATTERNS'],
|
||||
)
|
||||
]
|
||||
settings['PAGINATION_PATTERNS'] = sorted(
|
||||
pagination_rules,
|
||||
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',
|
||||
'FILES_TO_COPY',
|
||||
'IGNORE_FILES',
|
||||
'JINJA_EXTENSIONS',
|
||||
'PAGINATED_DIRECT_TEMPLATES',
|
||||
'PLUGINS',
|
||||
'STATIC_EXCLUDES',
|
||||
'STATIC_PATHS',
|
||||
'THEME_STATIC_PATHS',
|
||||
'ARTICLE_PATHS',
|
||||
'PAGE_PATHS',
|
||||
)
|
||||
for PATH_KEY in filter(lambda k: k in settings, path_keys):
|
||||
if isinstance(settings[PATH_KEY], six.string_types):
|
||||
logger.warning("Detected misconfiguration with %s setting "
|
||||
"(must be a list), falling back to the default",
|
||||
PATH_KEY)
|
||||
settings[PATH_KEY] = DEFAULT_CONFIG[PATH_KEY]
|
||||
|
||||
# Add {PAGE,ARTICLE}_PATHS to {ARTICLE,PAGE}_EXCLUDES
|
||||
mutually_exclusive = ('ARTICLE', 'PAGE')
|
||||
for type_1, type_2 in [mutually_exclusive, mutually_exclusive[::-1]]:
|
||||
try:
|
||||
from webassets.ext.jinja2 import AssetsExtension
|
||||
settings['JINJA_EXTENSIONS'].append(AssetsExtension)
|
||||
except ImportError:
|
||||
logger.warn("You must install the webassets module to use WEBASSETS.")
|
||||
settings['WEBASSETS'] = False
|
||||
includes = settings[type_1 + '_PATHS']
|
||||
excludes = settings[type_2 + '_EXCLUDES']
|
||||
for path in includes:
|
||||
if path not in excludes:
|
||||
excludes.append(path)
|
||||
except KeyError:
|
||||
continue # setting not specified, nothing to do
|
||||
|
||||
if 'OUTPUT_SOURCES_EXTENSION' in settings:
|
||||
if not isinstance(settings['OUTPUT_SOURCES_EXTENSION'], str):
|
||||
settings['OUTPUT_SOURCES_EXTENSION'] = _DEFAULT_CONFIG['OUTPUT_SOURCES_EXTENSION']
|
||||
logger.warn("Detected misconfiguration with OUTPUT_SOURCES_EXTENSION."
|
||||
" falling back to the default extension " +
|
||||
_DEFAULT_CONFIG['OUTPUT_SOURCES_EXTENSION'])
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,10 +1,47 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals, print_function
|
||||
from blinker import signal
|
||||
|
||||
# Run-level signals:
|
||||
|
||||
initialized = signal('pelican_initialized')
|
||||
finalized = signal('pelican_finalized')
|
||||
article_generate_context = signal('article_generate_context')
|
||||
article_generator_init = signal('article_generator_init')
|
||||
get_generators = signal('get_generators')
|
||||
pages_generate_context = signal('pages_generate_context')
|
||||
pages_generator_init = signal('pages_generator_init')
|
||||
get_writer = signal('get_writer')
|
||||
finalized = signal('pelican_finalized')
|
||||
|
||||
# Reader-level signals
|
||||
|
||||
readers_init = signal('readers_init')
|
||||
|
||||
# Generator-level signals
|
||||
|
||||
generator_init = signal('generator_init')
|
||||
|
||||
article_generator_init = signal('article_generator_init')
|
||||
article_generator_pretaxonomy = signal('article_generator_pretaxonomy')
|
||||
article_generator_finalized = signal('article_generator_finalized')
|
||||
article_generator_write_article = signal('article_generator_write_article')
|
||||
article_writer_finalized = signal('article_writer_finalized')
|
||||
|
||||
page_generator_init = signal('page_generator_init')
|
||||
page_generator_finalized = signal('page_generator_finalized')
|
||||
|
||||
static_generator_init = signal('static_generator_init')
|
||||
static_generator_finalized = signal('static_generator_finalized')
|
||||
|
||||
# Page-level signals
|
||||
|
||||
article_generator_preread = signal('article_generator_preread')
|
||||
article_generator_context = signal('article_generator_context')
|
||||
|
||||
page_generator_preread = signal('page_generator_preread')
|
||||
page_generator_context = signal('page_generator_context')
|
||||
|
||||
static_generator_preread = signal('static_generator_preread')
|
||||
static_generator_context = signal('static_generator_context')
|
||||
|
||||
content_object_init = signal('content_object_init')
|
||||
|
||||
# Writers signals
|
||||
content_written = signal('content_written')
|
||||
feed_written = signal('feed_written')
|
||||
|
|
|
|||
6
pelican/tests/TestPages/page_used_for_sorting_test.rst
Normal file
6
pelican/tests/TestPages/page_used_for_sorting_test.rst
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
A Page (Test) for sorting
|
||||
#########################
|
||||
|
||||
:slug: zzzz
|
||||
|
||||
When using title, should be first. When using slug, should be last.
|
||||
2
pelican/tests/__init__.py
Normal file
2
pelican/tests/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import logging
|
||||
logging.getLogger().addHandler(logging.NullHandler())
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
Rst with filename metadata
|
||||
##########################
|
||||
|
||||
:category: yeah
|
||||
:author: Alexis Métaireau
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
category: yeah
|
||||
author: Alexis Métaireau
|
||||
|
||||
Markdown with filename metadata
|
||||
===============================
|
||||
|
||||
|
|
@ -2,5 +2,6 @@ This is an article with category !
|
|||
##################################
|
||||
|
||||
:category: yeah
|
||||
:date: 1970-01-01
|
||||
|
||||
This article should be in 'yeah' category.
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
Article title
|
||||
#############
|
||||
|
||||
This is some content. With some stuff to "typogrify".
|
||||
THIS is some content. With some stuff to "typogrify"...
|
||||
|
||||
Now with added support for :abbr:`TLA (three letter acronym)`.
|
||||
15
pelican/tests/content/article_with_code_block.rst
Normal file
15
pelican/tests/content/article_with_code_block.rst
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
An Article With Code Block To Test Typogrify Ignore
|
||||
###################################################
|
||||
|
||||
An article with some code
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
x & y
|
||||
|
||||
A block quote:
|
||||
|
||||
x & y
|
||||
|
||||
Normal:
|
||||
x & y
|
||||
8
pelican/tests/content/article_with_comments.html
Normal file
8
pelican/tests/content/article_with_comments.html
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
Body content
|
||||
<!-- This comment is included (including extra whitespace) -->
|
||||
</body>
|
||||
</html>
|
||||
6
pelican/tests/content/article_with_keywords.html
Normal file
6
pelican/tests/content/article_with_keywords.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>This is a super article !</title>
|
||||
<meta name="keywords" content="foo, bar, foobar" />
|
||||
</head>
|
||||
</html>
|
||||
15
pelican/tests/content/article_with_markdown_and_footnote.md
Normal file
15
pelican/tests/content/article_with_markdown_and_footnote.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
Title: Article with markdown containing footnotes
|
||||
Date: 2012-10-31
|
||||
Modified: 2012-11-01
|
||||
Summary: Summary with **inline** markup *should* be supported.
|
||||
Multiline: Line Metadata should be handle properly.
|
||||
See syntax of Meta-Data extension of Python Markdown package:
|
||||
If a line is indented by 4 or more spaces,
|
||||
that line is assumed to be an additional line of the value
|
||||
for the previous keyword.
|
||||
A keyword may have as many lines as desired.
|
||||
|
||||
This is some content[^1] with some footnotes[^footnote]
|
||||
|
||||
[^1]: Numbered footnote
|
||||
[^footnote]: Named footnote
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
Title: マックOS X 10.8でパイソンとVirtualenvをインストールと設定
|
||||
Slug: python-virtualenv-on-mac-osx-mountain-lion-10.8
|
||||
Date: 2012-12-20
|
||||
Modified: 2012-12-22
|
||||
Tags: パイソン, マック
|
||||
Category: 指導書
|
||||
Summary: パイソンとVirtualenvをまっくでインストールする方法について明確に説明します。
|
||||
|
||||
Writing unicode is certainly fun.
|
||||
|
||||
パイソンとVirtualenvをまっくでインストールする方法について明確に説明します。
|
||||
|
||||
And let's mix languages.
|
||||
|
||||
первый пост
|
||||
|
||||
Now another.
|
||||
|
||||
İlk yazı çok özel değil.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
Title: Article with markdown and summary metadata multi
|
||||
Date: 2012-10-31
|
||||
Summary:
|
||||
A multi-line summary should be supported
|
||||
as well as **inline markup**.
|
||||
|
||||
This is some content.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
Title: Article with markdown and summary metadata single
|
||||
Date: 2012-10-30
|
||||
Summary: A single-line summary should be supported as well as **inline markup**.
|
||||
|
||||
This is some content.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
title: Test markdown File
|
||||
category: test
|
||||
|
||||
Test Markdown File Header
|
||||
=========================
|
||||
|
||||
Used for pelican test
|
||||
---------------------
|
||||
|
||||
This is another markdown test file. Uses the markdown extension.
|
||||
14
pelican/tests/content/article_with_md_extension.md
Normal file
14
pelican/tests/content/article_with_md_extension.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
Title: Test md File
|
||||
Category: test
|
||||
Tags: foo, bar, foobar
|
||||
Date: 2010-12-02 10:14
|
||||
Modified: 2010-12-02 10:20
|
||||
Summary: I have a lot to test
|
||||
|
||||
Test Markdown File Header
|
||||
=========================
|
||||
|
||||
Used for pelican test
|
||||
---------------------
|
||||
|
||||
The quick brown fox jumped over the lazy dog's back.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
title: Test md File
|
||||
title: Test mdown File
|
||||
category: test
|
||||
|
||||
Test Markdown File Header
|
||||
|
|
@ -7,4 +7,4 @@ Test Markdown File Header
|
|||
Used for pelican test
|
||||
---------------------
|
||||
|
||||
The quick brown fox jumped over the lazy dog's back.
|
||||
This is another markdown test file. Uses the mdown extension.
|
||||
15
pelican/tests/content/article_with_metadata.html
Normal file
15
pelican/tests/content/article_with_metadata.html
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>This is a super article !</title>
|
||||
<meta name="tags" content="foo, bar, foobar" />
|
||||
<meta name="date" content="2010-12-02 10:14" />
|
||||
<meta name="category" content="yeah" />
|
||||
<meta name="author" content="Alexis Métaireau" />
|
||||
<meta name="summary" content="Summary and stuff" />
|
||||
<meta name="custom_field" content="http://notmyidea.org" />
|
||||
</head>
|
||||
<body>
|
||||
Multi-line metadata should be supported
|
||||
as well as <strong>inline markup</strong>.
|
||||
</body>
|
||||
</html>
|
||||
13
pelican/tests/content/article_with_metadata.rst
Normal file
13
pelican/tests/content/article_with_metadata.rst
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
This is a super article !
|
||||
#########################
|
||||
|
||||
:tags: foo, bar, foobar
|
||||
:date: 2010-12-02 10:14
|
||||
:modified: 2010-12-02 10:20
|
||||
:category: yeah
|
||||
:author: Alexis Métaireau
|
||||
:summary:
|
||||
Multi-line metadata should be supported
|
||||
as well as **inline markup** and stuff to "typogrify"...
|
||||
:custom_field: http://notmyidea.org
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>This is a super article !</title>
|
||||
<meta name="tags" contents="foo, bar, foobar" />
|
||||
<meta name="date" contents="2010-12-02 10:14" />
|
||||
<meta name="category" contents="yeah" />
|
||||
<meta name="author" contents="Alexis Métaireau" />
|
||||
<meta name="summary" contents="Summary and stuff" />
|
||||
<meta name="custom_field" contents="http://notmyidea.org" />
|
||||
</head>
|
||||
<body>
|
||||
Multi-line metadata should be supported
|
||||
as well as <strong>inline markup</strong>.
|
||||
</body>
|
||||
</html>
|
||||
6
pelican/tests/content/article_with_multiple_authors.html
Normal file
6
pelican/tests/content/article_with_multiple_authors.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>This is an article with multiple authors!</title>
|
||||
<meta name="authors" content="First Author, Second Author" />
|
||||
</head>
|
||||
</html>
|
||||
6
pelican/tests/content/article_with_multiple_authors.rst
Normal file
6
pelican/tests/content/article_with_multiple_authors.rst
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
This is an article with multiple authors!
|
||||
#########################################
|
||||
|
||||
:date: 2014-02-09 02:20
|
||||
:modified: 2014-02-09 02:20
|
||||
:authors: First Author, Second Author
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>Article with Nonconformant HTML meta tags</title>
|
||||
<meta name="summary" content="Summary and stuff" />
|
||||
</head>
|
||||
<body>
|
||||
Multi-line metadata should be supported
|
||||
as well as <strong>inline markup</strong>.
|
||||
</body>
|
||||
</html>
|
||||
8
pelican/tests/content/article_with_null_attributes.html
Normal file
8
pelican/tests/content/article_with_null_attributes.html
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
Ensure that empty attributes are copied properly.
|
||||
<input name="test" disabled style="" />
|
||||
</body>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue