mirror of
https://github.com/getpelican/pelican.git
synced 2025-10-15 20:28:56 +02:00
Merge branch 'master' into pathdocs
This commit is contained in:
commit
e9f81d3f9c
389 changed files with 10281 additions and 4330 deletions
15
.editorconfig
Normal file
15
.editorconfig
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.py]
|
||||
max_line_length = 79
|
||||
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
custom: https://donate.getpelican.com
|
||||
liberapay: pelican
|
||||
31
.github/stale.yml
vendored
Normal file
31
.github/stale.yml
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 60
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||
daysUntilClose: 30
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: true
|
||||
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: true
|
||||
|
||||
# Set to true to ignore issues with an assignee (defaults to false)
|
||||
exemptAssignees: true
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your participation and understanding.
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 1
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
only: issues
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -11,5 +11,8 @@ tags
|
|||
.tox
|
||||
.coverage
|
||||
htmlcov
|
||||
six-*.egg/
|
||||
*.orig
|
||||
venv
|
||||
samples/output
|
||||
*.pem
|
||||
poetry.lock
|
||||
|
|
|
|||
14
.pre-commit-config.yaml
Normal file
14
.pre-commit-config.yaml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# See https://pre-commit.com/hooks.html for info on hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.3.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-ast
|
||||
- id: check-toml
|
||||
- id: check-yaml
|
||||
- id: debug-statements
|
||||
- id: detect-private-key
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
exclude: ^pelican/tests/output/
|
||||
44
.travis.yml
44
.travis.yml
|
|
@ -1,18 +1,25 @@
|
|||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.6"
|
||||
env:
|
||||
- TOX_ENV=docs
|
||||
- TOX_ENV=flake8
|
||||
- TOX_ENV=py27
|
||||
- TOX_ENV=py33
|
||||
- TOX_ENV=py34
|
||||
- TOX_ENV=py35
|
||||
global:
|
||||
- PYPI_USERNAME=autopub
|
||||
- secure: "h5V/+YL+CrqvfAesNkSb824Ngk5x+f0eFzj/LBbmnzjvArKAmc6R6WGyx8SDD7WF/PlaTf0M1fH3a7pjIS8Ee+TS1Rb0Lt1HPqUs1yntg1+Js2ZQp3p20wfsDc+bZ4/2g8xLsSMv1EJ4np7/GJ5fXqpSxjr/Xs5LYA7ZLwNNwDw="
|
||||
- secure: "GiDFfmjH7uzYNnkjQMV/mIkbRdmgkGmtbFPeaj9taBNA5tPp3IBt3GOOS6UL/zm9xiwu9Xo6sxZWkGzY19Hsdv28YPH34N3abo0QSnz4IGiHs152Hi7Qi6Tb0QkT5D3OxuSIm8LmFL7+su89Q7vBFowrT6HL1Mn8CDDWSj3eqbo="
|
||||
- TWINE_USERNAME=$PYPI_USERNAME
|
||||
- TWINE_PASSWORD=$PYPI_PASSWORD
|
||||
matrix:
|
||||
- TOX_ENV=docs
|
||||
- TOX_ENV=flake8
|
||||
- TOX_ENV=py35
|
||||
- TOX_ENV=py36
|
||||
matrix:
|
||||
include:
|
||||
- python: 3.6
|
||||
- python: 3.7
|
||||
sudo: true
|
||||
dist: xenial
|
||||
env:
|
||||
- TOX_ENV=py36
|
||||
- TOX_ENV=py37
|
||||
addons:
|
||||
apt_packages:
|
||||
- pandoc
|
||||
|
|
@ -22,8 +29,25 @@ before_install:
|
|||
install:
|
||||
- pip install tox==2.5.0
|
||||
script: tox -e $TOX_ENV
|
||||
before_deploy:
|
||||
- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then travis_terminate 0; fi'
|
||||
- pip install githubrelease
|
||||
- pip install --pre autopub
|
||||
- autopub check || travis_terminate 0
|
||||
- pip install poetry
|
||||
- pip install twine
|
||||
- git checkout ${TRAVIS_BRANCH}
|
||||
- git remote set-url origin https://$GITHUB_TOKEN@github.com/$TRAVIS_REPO_SLUG
|
||||
deploy:
|
||||
provider: script
|
||||
script: autopub deploy
|
||||
skip_cleanup: true
|
||||
on:
|
||||
branch: master
|
||||
python: "3.7"
|
||||
# The channel name "irc.freenode.org#pelican" is encrypted against getpelican/pelican to prevent IRC spam of forks
|
||||
notifications:
|
||||
irc:
|
||||
channels:
|
||||
- "irc.freenode.org#pelican"
|
||||
- secure: "JP57f61QovrhmLoAF6oPOzIK2aXGfSO06FHg7yiuKBOWMiaxQejZUGJX919muCLhWJXDugsviIqCMoAWwNV3o1WQbqIr+G5TR+N9MrtCs4Zi6vpGj09bR8giKUKx+PPKEoe1Ew56E4y2LxzGO4Lj9hZx8M2YVdwPNWrWZgp6WXE="
|
||||
on_success: change
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@ Before you ask for help, please make sure you do the following:
|
|||
|
||||
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 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
|
||||
(2) settings files, and (3) ``make``/``invoke`` automation wrappers. If you can't
|
||||
reproduce your problem when using the following steps to generate your site,
|
||||
then the problem is almost certainly with your chosen theme and/or settings
|
||||
file (and not Pelican itself)::
|
||||
|
|
@ -58,7 +58,7 @@ publicly-accessible location:
|
|||
* Upload detailed and **complete** output logs and backtraces (remember to add
|
||||
the ``--debug`` flag: ``pelican --debug content [...]``)
|
||||
|
||||
.. _documentation: http://docs.getpelican.com/
|
||||
.. _documentation: https://docs.getpelican.com/
|
||||
.. _`paste service`: https://dpaste.de/
|
||||
|
||||
Once the above preparation is ready, you can contact people willing to help via
|
||||
|
|
@ -71,13 +71,13 @@ 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.
|
||||
* If you don't have an IRC client handy, use the webchat_.
|
||||
* 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/
|
||||
.. _`IRC link`: irc://irc.freenode.net/pelican
|
||||
.. _`freenode IRC network`: https://freenode.net/
|
||||
|
||||
|
||||
Contributing code
|
||||
|
|
@ -94,9 +94,17 @@ 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
|
||||
For example, if you're working 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.
|
||||
* Add a ``RELEASE.md`` file in the root of the project that contains the
|
||||
release type (major, minor, patch) and a summary of the changes that will be
|
||||
used as the release changelog entry. For example::
|
||||
|
||||
Release type: minor
|
||||
|
||||
Reload browser window upon changes to content, settings, or theme
|
||||
|
||||
* 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.
|
||||
|
|
@ -112,19 +120,25 @@ Using Git and GitHub
|
|||
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.
|
||||
`hub pull-request -i [ISSUE] <https://hub.github.com/hub-pull-request.1.html>`_
|
||||
to turn your GitHub issue into a pull request containing your code.
|
||||
* After you have issued a pull request, the continuous integration (CI) system
|
||||
will run the test suite for all supported Python versions and check for PEP8
|
||||
compliance. If any of these checks fail, you should fix them. (If tests fail
|
||||
on the CI system but seem to pass locally, ensure that local test runs aren't
|
||||
skipping any tests.)
|
||||
|
||||
Contribution quality standards
|
||||
------------------------------
|
||||
|
||||
* Adhere to `PEP8 coding standards`_ whenever possible. This can be eased via
|
||||
the `pep8 <http://pypi.python.org/pypi/pep8>`_ or `flake8
|
||||
<http://pypi.python.org/pypi/flake8/>`_ tools, the latter of which in
|
||||
* Adhere to `PEP8 coding standards`_. This can be eased via the `pycodestyle
|
||||
<https://pypi.org/project/pycodestyle>`_ or `flake8
|
||||
<https://pypi.org/project/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.
|
||||
code/formatting can be improved. If you are relying on your editor for PEP8
|
||||
compliance, note that the line length specified by PEP8 is 79 (excluding the
|
||||
line break).
|
||||
* Ensure your code is compatible with the `officially-supported Python releases`_.
|
||||
* 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
|
||||
|
|
@ -133,12 +147,12 @@ Contribution quality standards
|
|||
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
|
||||
.. _`plugin`: https://docs.getpelican.com/en/latest/plugins.html
|
||||
.. _`#pelican IRC channel`: https://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
|
||||
.. _`Run all the tests`: https://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/
|
||||
.. _`PEP8 coding standards`: https://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
|
||||
.. _`officially-supported Python releases`: https://devguide.python.org/#status-of-python-branches
|
||||
|
|
|
|||
6
LICENSE
6
LICENSE
|
|
@ -1,7 +1,7 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://www.fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
|
@ -643,7 +643,7 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
|
|
@ -658,4 +658,4 @@ specific requirements.
|
|||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
include *.rst
|
||||
recursive-include pelican *.html *.css *png *.in *.rst *.markdown *.md *.mkd *.xml *.py
|
||||
include LICENSE THANKS docs/changelog.rst
|
||||
recursive-include pelican *.html *.css *png *.rst *.markdown *.md *.mkd *.xml *.py
|
||||
include LICENSE THANKS docs/changelog.rst pyproject.toml
|
||||
|
|
|
|||
12
README.rst
12
README.rst
|
|
@ -47,13 +47,13 @@ Why the name "Pelican"?
|
|||
|
||||
.. Links
|
||||
|
||||
.. _Python: http://www.python.org/
|
||||
.. _Python: https://www.python.org/
|
||||
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
|
||||
.. _Markdown: http://daringfireball.net/projects/markdown/
|
||||
.. _Jinja2: http://jinja.pocoo.org/
|
||||
.. _Pygments: http://pygments.org/
|
||||
.. _`Pelican's documentation`: http://docs.getpelican.com/
|
||||
.. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html
|
||||
.. _Markdown: https://daringfireball.net/projects/markdown/
|
||||
.. _Jinja2: https://palletsprojects.com/p/jinja/
|
||||
.. _Pygments: https://pygments.org/
|
||||
.. _`Pelican's documentation`: https://docs.getpelican.com/
|
||||
.. _`Pelican's internals`: https://docs.getpelican.com/en/latest/internals.html
|
||||
.. _`hosted on GitHub`: https://github.com/getpelican/pelican
|
||||
|
||||
.. |build-status| image:: https://img.shields.io/travis/getpelican/pelican/master.svg
|
||||
|
|
|
|||
8
THANKS
8
THANKS
|
|
@ -1,11 +1,12 @@
|
|||
Pelican is a project originally created by Alexis Métaireau
|
||||
<http://notmyidea.org/>, but there are a large number of people that have
|
||||
<https://blog.notmyidea.org/> and subsequently maintained by Justin Mayer
|
||||
<https://justinmayer.com/>, 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
|
||||
|
||||
If you want to contribute, check the documentation section about how to do so:
|
||||
<http://docs.getpelican.com/en/latest/contribute.html>
|
||||
<https://docs.getpelican.com/en/latest/contribute.html>
|
||||
|
||||
Aaron Kavlie
|
||||
Abhishek L
|
||||
|
|
@ -91,6 +92,7 @@ Joshua Adelman
|
|||
Julian Berman
|
||||
Justin Mayer
|
||||
Kevin Deldycke
|
||||
Kevin Yap
|
||||
Kyle Fuller
|
||||
Laureline Guerin
|
||||
Leonard Huang
|
||||
|
|
@ -116,6 +118,7 @@ Nico Di Rocco
|
|||
Nicolas Duhamel
|
||||
Nicolas Perriault
|
||||
Nicolas Steinmetz
|
||||
Paolo Melchiorre
|
||||
Paul Asselin
|
||||
Pavel Puchkin
|
||||
Perry Roper
|
||||
|
|
@ -155,6 +158,7 @@ Tshepang Lekhonkhobe
|
|||
Valentin-Costel Hăloiu
|
||||
Vlad Niculae
|
||||
William Light
|
||||
William Minchin
|
||||
Wladislaw Merezhko
|
||||
W. Trevor King
|
||||
Zoresvit
|
||||
|
|
|
|||
32
bumpr.rc
32
bumpr.rc
|
|
@ -1,32 +0,0 @@
|
|||
[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
|
||||
setup.py
|
||||
|
||||
[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[[:space:]]*=.*/last_stable = '{version}'/" docs/conf.py
|
||||
BIN
docs/_static/overall.png
vendored
BIN
docs/_static/overall.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 4.9 KiB |
|
|
@ -1,10 +1,99 @@
|
|||
Release history
|
||||
###############
|
||||
|
||||
Next release
|
||||
============
|
||||
4.2.0 - 2019-10-17
|
||||
==================
|
||||
|
||||
* New signal: ``feed_generated``
|
||||
* Support inline SVGs; don't treat titles in SVGs as HTML titles
|
||||
* Add category to feeds (in addition to tags)
|
||||
* Improve content metadata field docs
|
||||
* Add docs for including other Markdown/reST files in content
|
||||
|
||||
4.1.3 - 2019-10-09
|
||||
==================
|
||||
|
||||
* Fix quick-start docs regarding `pelican --listen`
|
||||
* Set default listen address to 127.0.0.1
|
||||
* Add extra/optional Markdown dependency to setup.py
|
||||
* Use correct SSH port syntax for rsync in tasks.py
|
||||
* Place all deprecated settings handling together
|
||||
* Add related project URLs for display on PyPI
|
||||
* Skip some tests on Windows that can't pass due to filesystem differences
|
||||
|
||||
4.1.2 - 2019-09-23
|
||||
==================
|
||||
|
||||
Fix pelican.settings.load_source to avoid caching issues - PR #2621
|
||||
|
||||
4.1.1 - 2019-08-23
|
||||
==================
|
||||
|
||||
* Add AutoPub to auto-publish releases on PR merge
|
||||
* Add CSS classes for reStructuredText figures
|
||||
* Pass `argv` to Pelican `main` entrypoint
|
||||
* Set default content status to a blank string rather than `None`
|
||||
|
||||
4.1.0 - 2019-07-14
|
||||
==================
|
||||
|
||||
* Live browser reload upon changed files (provided via Invoke task)
|
||||
* Add ``pyproject.toml``, managed by Poetry
|
||||
* Support for invoking ``python -m pelican``
|
||||
* Add relative source path attribute to content
|
||||
* Allow directories in ``EXTRA_PATH_METADATA``
|
||||
* Add ``all_articles`` variable to period pages (for recent posts functionality)
|
||||
* Improve debug mode output
|
||||
* Remove blank or duplicate summaries from Atom feed
|
||||
* Fix bugs in pagination, pelican-import, pelican-quickstart, and feed importer
|
||||
|
||||
4.0.1 (2018-11-30)
|
||||
==================
|
||||
|
||||
* Refactor ``pelican.server`` logging
|
||||
* Fix bug in which all static files were processed as "draft"
|
||||
* Bug fixes for Invoke/Makefile automation, Importer, and other miscellanea
|
||||
|
||||
If upgrading from 3.7.x or earlier, please note that slug-related settings in
|
||||
4.0+ use ``{slug}`` and/or ``{lang}`` rather than ``%s``. If ``%s``-style
|
||||
settings are encountered, Pelican will emit a warning and fall back to the
|
||||
default setting. Some user-submitted themes might try to format setting values
|
||||
but fail upon site build with a ``TypeError``. In such cases, the theme needs
|
||||
to be updated. For example, instead of ``TAG_FEED_ATOM|format(tag.slug)``, use
|
||||
``TAG_FEED_ATOM.format(slug=tag.slug)``
|
||||
|
||||
4.0.0 (2018-11-13)
|
||||
==================
|
||||
|
||||
* Replace ``develop_server.sh`` script with ``pelican --listen``
|
||||
* Improved copy/link behavior for large static files (e.g., videos)
|
||||
* New ``{static}`` syntax to link to static content; content linked to by
|
||||
``{static}`` and ``{attach}`` is automatically copied over even if not in
|
||||
``STATIC_PATHS``
|
||||
* Pages can now have ``draft`` status
|
||||
* Show current settings via new ``--print-settings`` flag
|
||||
* All settings for slugs now use ``{slug}`` and/or ``{lang}`` rather than
|
||||
``%s``. If ``%s``-style settings are encountered, Pelican will emit a warning
|
||||
and fallback to the default setting.
|
||||
* New signals: ``feed_generated`` and ``page_generated_write_page``
|
||||
* Replace Fabric with Invoke and ``fabfile.py`` template with ``tasks.py``
|
||||
* Replace ``PAGINATED_DIRECT_TEMPLATES`` by ``PAGINATED_TEMPLATES``, extending
|
||||
control over pagination to all templates and making page size variable
|
||||
* Replace ``SLUG_SUBSTITUTIONS`` (and friends) by ``SLUG_REGEX_SUBSTITUTIONS``
|
||||
for more finegrained control
|
||||
* ``'{base_name}'`` value in ``PAGINATION_PATTERNS`` setting no longer strips
|
||||
``'bar'`` from ``'foo/bar.html'`` (unless ``'bar' == 'index'``).
|
||||
* ``ARTICLE_ORDER_BY`` and ``PAGE_ORDER_BY`` now also affect 1) category, tag
|
||||
and author pages 2) feeds 3) draft and hidden articles and pages
|
||||
* New ``ARTICLE_TRANSLATION_ID`` and ``PAGE_TRANSLATION_ID`` settings to
|
||||
specify metadata attributes used to identify/disable translations
|
||||
* Make the HTML reader parse multiple occurrences of metadata tags as a list
|
||||
* New Blogger XML backup importer
|
||||
* Wordpress importer now updates file links to point to local copies if the
|
||||
files were downloaded with ``--wp-attach``.
|
||||
* Importer no longer inserts extra newlines, to prevent breaking of HTML
|
||||
attributes.
|
||||
* Pelican server now prioritises ``foo.html`` and ``foo/index.html`` over
|
||||
``foo/`` when resolving ``foo``.
|
||||
|
||||
3.7.1 (2017-01-10)
|
||||
==================
|
||||
|
|
@ -91,8 +180,8 @@ Next release
|
|||
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.
|
||||
* 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.
|
||||
|
|
@ -195,8 +284,10 @@ Next release
|
|||
* 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``
|
||||
* 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
|
||||
|
|
@ -204,7 +295,8 @@ Next release
|
|||
* 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 ``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
|
||||
|
|
@ -275,7 +367,8 @@ Next release
|
|||
* Added translations
|
||||
* Added a way to use cleaner URLs with a rewrite url module (or equivalent)
|
||||
* Added a tag cloud
|
||||
* Added an autoreloading feature: the blog is automatically regenerated each time a modification is detected
|
||||
* Added an autoreloading feature: the blog is automatically regenerated each
|
||||
time a modification is detected
|
||||
* Translate the documentation into French
|
||||
* Import a blog from an RSS feed
|
||||
* Pagination support
|
||||
|
|
|
|||
12
docs/conf.py
12
docs/conf.py
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
|
@ -17,11 +17,11 @@ extensions = ['sphinx.ext.autodoc',
|
|||
source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
project = 'Pelican'
|
||||
copyright = '2015, Alexis Metaireau and contributors'
|
||||
copyright = '2010 – present, Justin Mayer, Alexis Metaireau, and contributors'
|
||||
exclude_patterns = ['_build']
|
||||
release = __version__
|
||||
version = '.'.join(release.split('.')[:1])
|
||||
last_stable = '3.7.1'
|
||||
last_stable = __version__
|
||||
rst_prolog = '''
|
||||
.. |last_stable| replace:: :pelican-doc:`{0}`
|
||||
'''.format(last_stable)
|
||||
|
|
@ -30,7 +30,7 @@ rst_prolog = '''
|
|||
pygments_style = 'sphinx'
|
||||
|
||||
extlinks = {
|
||||
'pelican-doc': ('http://docs.getpelican.com/%s/', '')
|
||||
'pelican-doc': ('https://docs.getpelican.com/%s/', '')
|
||||
}
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
|
@ -68,14 +68,14 @@ def setup(app):
|
|||
|
||||
# -- Options for LaTeX output -------------------------------------------------
|
||||
latex_documents = [
|
||||
('index', 'Pelican.tex', 'Pelican Documentation', 'Alexis Métaireau',
|
||||
('index', 'Pelican.tex', 'Pelican Documentation', 'Justin Mayer',
|
||||
'manual'),
|
||||
]
|
||||
|
||||
# -- Options for manual page output -------------------------------------------
|
||||
man_pages = [
|
||||
('index', 'pelican', 'pelican documentation',
|
||||
['Alexis Métaireau'], 1),
|
||||
['Justin Mayer'], 1),
|
||||
('pelican-themes', 'pelican-themes', 'A theme manager for Pelican',
|
||||
['Mickaël Raybaud'], 1),
|
||||
('themes', 'pelican-theming', 'How to create themes for Pelican',
|
||||
|
|
|
|||
212
docs/content.rst
212
docs/content.rst
|
|
@ -11,7 +11,7 @@ The idea behind "pages" is that they are usually not temporal in nature and are
|
|||
used for content that does not change very often (e.g., "About" or "Contact"
|
||||
pages).
|
||||
|
||||
You can find sample content in the repository at: ``pelican/samples/content/``
|
||||
You can find sample content in the repository at ``samples/content/``.
|
||||
|
||||
.. _internal_metadata:
|
||||
|
||||
|
|
@ -70,6 +70,31 @@ Metadata syntax for Markdown posts should follow this pattern::
|
|||
|
||||
This is the content of my super blog post.
|
||||
|
||||
You can also have your own metadata keys (so long as they don't conflict with
|
||||
reserved metadata keywords) for use in your templates. The following table
|
||||
contains a list of reserved metadata keywords:
|
||||
|
||||
=============== ===============================================================
|
||||
Metadata Description
|
||||
=============== ===============================================================
|
||||
``title`` Title of the article or page
|
||||
``date`` Publication date (e.g., ``YYYY-MM-DD HH:SS``)
|
||||
``modified`` Modification date (e.g., ``YYYY-MM-DD HH:SS``)
|
||||
``tags`` Content tags, separated by commas
|
||||
``keywords`` Content keywords, separated by commas (HTML content only)
|
||||
``category`` Content category (one only — not multiple)
|
||||
``slug`` Identifier used in URLs and translations
|
||||
``author`` Content author, when there is only one
|
||||
``authors`` Content authors, when there are multiple
|
||||
``summary`` Brief description of content for index pages
|
||||
``lang`` Content language ID (``en``, ``fr``, etc.)
|
||||
``translation`` Is content is a translation of another (``true`` or ``false``)
|
||||
``status`` Content status: ``draft``, ``hidden``, or ``published``
|
||||
``template`` Name of template to use to generate content (without extension)
|
||||
``save_as`` Save content to this relative file path
|
||||
``url`` URL to use for this article/page
|
||||
=============== ===============================================================
|
||||
|
||||
Readers for additional formats (such as AsciiDoc_) are available via plugins.
|
||||
Refer to `pelican-plugins`_ repository for those.
|
||||
|
||||
|
|
@ -93,12 +118,12 @@ interprets the HTML in a very straightforward manner, reading metadata from
|
|||
</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
|
||||
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:
|
||||
Note that, aside from the title, none of this content 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
|
||||
|
|
@ -108,6 +133,15 @@ 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`__.
|
||||
|
||||
So the title is the only required metadata. If that bothers you, worry not.
|
||||
Instead of manually specifying a title in your metadata each time, you can use
|
||||
the source content file name as the title. For example, a Markdown source file
|
||||
named ``Publishing via Pelican.md`` would automatically be assigned a title of
|
||||
*Publishing via Pelican*. If you would prefer this behavior, add the following
|
||||
line to your settings file::
|
||||
|
||||
FILENAME_METADATA = '(?P<title>.*)'
|
||||
|
||||
.. note::
|
||||
|
||||
When experimenting with different settings (especially the metadata
|
||||
|
|
@ -117,12 +151,13 @@ the W3C's `suggested subset ISO 8601`__.
|
|||
|
||||
__ `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.
|
||||
``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.
|
||||
``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
|
||||
|
|
@ -152,6 +187,34 @@ 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.
|
||||
|
||||
Static content
|
||||
==============
|
||||
|
||||
Static files are files other than articles and pages that are copied to the
|
||||
output folder as-is, without processing. You can control which static files
|
||||
are copied over with 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. In addition,
|
||||
static files that are explicitly linked to are included (see below).
|
||||
|
||||
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.
|
||||
|
||||
.. _ref-linking-to-internal-content:
|
||||
|
||||
Linking to internal content
|
||||
|
|
@ -205,13 +268,10 @@ and ``article2.md``::
|
|||
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.
|
||||
You can link to static content using ``{static}path/to/file``. Files linked to
|
||||
with this syntax will automatically be copied to the output directory, even if
|
||||
the source directories containing them are not included in the ``STATIC_PATHS``
|
||||
setting of the project's ``pelicanconf.py`` file.
|
||||
|
||||
For example, a project's content directory might be structured like this::
|
||||
|
||||
|
|
@ -225,48 +285,28 @@ For example, a project's content directory might be structured like this::
|
|||
|
||||
``test.md`` would include::
|
||||
|
||||

|
||||
[Our Menu]({filename}/pdfs/menu.pdf)
|
||||
|
||||
``pelicanconf.py`` would include::
|
||||
|
||||
STATIC_PATHS = ['images', 'pdfs']
|
||||

|
||||
[Our Menu]({static}/pdfs/menu.pdf)
|
||||
|
||||
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.
|
||||
If you use ``{static}`` to link to an article or a page, this will be turned
|
||||
into a link to its source code.
|
||||
|
||||
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
|
||||
like the ``{static}`` 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.
|
||||
This only works for linking to static files.
|
||||
|
||||
For example, a project's content directory might be structured like this::
|
||||
|
||||
|
|
@ -282,7 +322,6 @@ For example, a project's content directory might be structured like this::
|
|||
``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'
|
||||
|
|
@ -312,7 +351,7 @@ 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}``.
|
||||
After the first link, Pelican will treat ``{attach}`` like ``{static}``.
|
||||
This avoids breaking the already-processed links.
|
||||
|
||||
**Be careful when linking to a file from multiple documents:**
|
||||
|
|
@ -326,7 +365,7 @@ 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
|
||||
these precautions are not possible, consider using ``{static}`` 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``.)
|
||||
|
|
@ -345,12 +384,49 @@ You can link to authors, categories, index and tags using the ``{author}name``,
|
|||
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``.
|
||||
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.
|
||||
extensions or reST directives. Similarly, Pelican also still supports linking
|
||||
to static content with ``{filename}``. The syntax was changed to ``{static}``
|
||||
to allow linking to both generated articles and pages and their static sources.
|
||||
|
||||
Support for the old syntax may eventually be removed.
|
||||
|
||||
Including other files
|
||||
---------------------
|
||||
Both Markdown and reStructuredText syntaxes provide mechanisms for this.
|
||||
|
||||
Following below are some examples for **reStructuredText** using `the include directive`_:
|
||||
|
||||
.. code-block:: rst
|
||||
|
||||
.. include:: file.rst
|
||||
|
||||
Include a fragment of a file delimited by two identifiers, highlighted as C++ (slicing based on line numbers is also possible):
|
||||
|
||||
.. code-block:: rst
|
||||
|
||||
.. include:: main.cpp
|
||||
:code: c++
|
||||
:start-after: // begin
|
||||
:end-before: // end
|
||||
|
||||
Include a raw HTML file (or an inline SVG) and put it directly into the output without any processing:
|
||||
|
||||
.. code-block:: rst
|
||||
|
||||
.. raw:: html
|
||||
:file: table.html
|
||||
|
||||
For **Markdown**, one must rely on an extension. For example, using the `mdx_include plugin`_:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
```html
|
||||
{! template.html !}
|
||||
```
|
||||
|
||||
|
||||
Importing an existing site
|
||||
|
|
@ -375,8 +451,9 @@ of available translations for that article.
|
|||
language. For such advanced functionality the `i18n_subsites
|
||||
plugin`_ can be used.
|
||||
|
||||
Pelican uses the article's URL "slug" to determine if two or more articles are
|
||||
translations of one another. The slug can be set manually in the file's
|
||||
By default, Pelican uses the article's URL "slug" to determine if two or more
|
||||
articles are translations of one another. (This can be changed with the
|
||||
``ARTICLE_TRANSLATION_ID`` setting.) The slug can be set manually in the file's
|
||||
metadata; if not set explicitly, Pelican will auto-generate the slug from the
|
||||
title of the article.
|
||||
|
||||
|
|
@ -452,7 +529,7 @@ indenting both the identifier and the code::
|
|||
print("The path-less shebang syntax *will* show line numbers.")
|
||||
|
||||
The specified identifier (e.g. ``python``, ``ruby``) should be one that
|
||||
appears on the `list of available lexers <http://pygments.org/docs/lexers/>`_.
|
||||
appears on the `list of available lexers <https://pygments.org/docs/lexers/>`_.
|
||||
|
||||
When using reStructuredText the following options are available in the
|
||||
code-block directive:
|
||||
|
|
@ -493,7 +570,7 @@ 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
|
||||
`Pygments documentation <https://pygments.org/docs/formatters/>`_ for more
|
||||
details on each of the options.
|
||||
|
||||
For example, the following code block enables line numbers, starting at 153,
|
||||
|
|
@ -522,13 +599,14 @@ your settings file.
|
|||
Publishing drafts
|
||||
=================
|
||||
|
||||
If you want to publish an article as a draft (for friends to review before
|
||||
publishing, for example), you can add a ``Status: draft`` attribute to its
|
||||
metadata. That article will then be output to the ``drafts`` folder and not
|
||||
If you want to publish an article or a page as a draft (for friends to review
|
||||
before publishing, for example), you can add a ``Status: draft`` attribute to
|
||||
its metadata. That article will then be output to the ``drafts`` folder and not
|
||||
listed on the index page nor on any category or tag page.
|
||||
|
||||
If your articles should be automatically published as a draft (to not accidentally
|
||||
publish an article before it is finished) include the status in the ``DEFAULT_METADATA``::
|
||||
If your articles should be automatically published as a draft (to not
|
||||
accidentally publish an article before it is finished) include the status in
|
||||
the ``DEFAULT_METADATA``::
|
||||
|
||||
DEFAULT_METADATA = {
|
||||
'status': 'draft',
|
||||
|
|
@ -537,9 +615,11 @@ publish an article before it is finished) include the status in the ``DEFAULT_ME
|
|||
To publish a post when the default status is ``draft``, update the post's
|
||||
metadata to include ``Status: published``.
|
||||
|
||||
.. _W3C ISO 8601: http://www.w3.org/TR/NOTE-datetime
|
||||
.. _AsciiDoc: http://www.methods.co.nz/asciidoc/
|
||||
.. _pelican-plugins: http://github.com/getpelican/pelican-plugins
|
||||
.. _Markdown Extensions: http://pythonhosted.org/Markdown/extensions/
|
||||
.. _CodeHilite extension: http://pythonhosted.org/Markdown/extensions/code_hilite.html#syntax
|
||||
.. _i18n_subsites plugin: http://github.com/getpelican/pelican-plugins/tree/master/i18n_subsites
|
||||
.. _W3C ISO 8601: https://www.w3.org/TR/NOTE-datetime
|
||||
.. _AsciiDoc: https://www.methods.co.nz/asciidoc/
|
||||
.. _pelican-plugins: https://github.com/getpelican/pelican-plugins
|
||||
.. _Markdown Extensions: https://python-markdown.github.io/extensions/
|
||||
.. _CodeHilite extension: https://python-markdown.github.io/extensions/code_hilite/#syntax
|
||||
.. _i18n_subsites plugin: https://github.com/getpelican/pelican-plugins/tree/master/i18n_subsites
|
||||
.. _the include directive: http://docutils.sourceforge.net/docs/ref/rst/directives.html#include
|
||||
.. _mdx_include plugin: https://github.com/neurobin/mdx_include
|
||||
|
|
|
|||
|
|
@ -7,138 +7,169 @@ 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.
|
||||
When doing so, please consider the following guidelines.
|
||||
|
||||
.. include:: ../CONTRIBUTING.rst
|
||||
|
||||
Setting up the development environment
|
||||
======================================
|
||||
|
||||
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::
|
||||
While there are many ways to set up one's development environment, the following
|
||||
instructions will utilize Pip_ and Poetry_. These tools facilitate managing
|
||||
virtual environments for separate Python projects that are isolated from one
|
||||
another, so you can use different packages (and package versions) for each.
|
||||
|
||||
$ pip install virtualenv
|
||||
Please note that Python 3.6+ is required for Pelican development.
|
||||
|
||||
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.
|
||||
*(Optional)* If you prefer to install Poetry once for use with multiple projects,
|
||||
you can install it via::
|
||||
|
||||
To create and activate a virtual environment, use the following syntax::
|
||||
curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python
|
||||
|
||||
$ virtualenv ~/virtualenvs/pelican
|
||||
$ cd ~/virtualenvs/pelican
|
||||
$ . bin/activate
|
||||
Point your web browser to the `Pelican repository`_ and tap the **Fork** button
|
||||
at top-right. Then clone the source for your fork and add the upstream project
|
||||
as a Git remote::
|
||||
|
||||
To clone the Pelican source::
|
||||
mkdir ~/projects
|
||||
git clone https://github.com/YOUR_USERNAME/pelican.git ~/projects/pelican
|
||||
cd ~/projects/pelican
|
||||
git remote add upstream https://github.com/getpelican/pelican.git
|
||||
|
||||
$ git clone https://github.com/getpelican/pelican.git src/pelican
|
||||
While Poetry can dynamically create and manage virtual environments, we're going
|
||||
to manually create and activate a virtual environment::
|
||||
|
||||
To install the development dependencies::
|
||||
mkdir ~/virtualenvs
|
||||
python3 -m venv ~/virtualenvs/pelican
|
||||
source ~/virtualenvs/pelican/bin/activate
|
||||
|
||||
$ cd src/pelican
|
||||
$ pip install -r requirements/developer.pip
|
||||
Install the needed dependencies and set up the project::
|
||||
|
||||
To install Pelican and its dependencies::
|
||||
pip install -e ~/projects/pelican invoke
|
||||
invoke setup
|
||||
|
||||
$ python setup.py develop
|
||||
Your local environment should now be ready to go!
|
||||
|
||||
Or using ``pip``::
|
||||
.. _Pip: https://pip.pypa.io/
|
||||
.. _Poetry: https://poetry.eustace.io/docs/#installation
|
||||
.. _Pelican repository: https://github.com/getpelican/pelican
|
||||
|
||||
$ pip install -e .
|
||||
Development
|
||||
===========
|
||||
|
||||
To conveniently test on multiple Python versions, we also provide a .tox file.
|
||||
Once Pelican has been set up for local development, create a topic branch for
|
||||
your bug fix or feature::
|
||||
|
||||
git checkout -b name-of-your-bugfix-or-feature
|
||||
|
||||
Building the docs
|
||||
=================
|
||||
|
||||
If you make changes to the documentation, you should preview your changes
|
||||
before committing them::
|
||||
|
||||
$ pip install -r requirements/docs.pip
|
||||
$ cd src/pelican/docs
|
||||
$ make html
|
||||
|
||||
Open ``_build/html/index.html`` in your browser to preview the documentation.
|
||||
Now you can make changes to Pelican, its documentation, and/or other aspects of
|
||||
the project.
|
||||
|
||||
Running the test suite
|
||||
======================
|
||||
----------------------
|
||||
|
||||
Each time you add a feature, there are two things to do regarding tests:
|
||||
check that the existing tests pass, and add tests for the new feature
|
||||
or bugfix.
|
||||
Each time you make changes to Pelican, there are two things to do regarding
|
||||
tests: check that the existing tests pass, and add tests for any new features
|
||||
or bug fixes. The tests are located in ``pelican/tests``, and you can run them
|
||||
via::
|
||||
|
||||
The tests live in ``pelican/tests`` and you can run them using the
|
||||
"discover" feature of ``unittest``::
|
||||
invoke tests
|
||||
|
||||
$ python -Wd -m unittest discover
|
||||
In addition to running the test suite, the above invocation will also check code
|
||||
style and let you know whether non-conforming patterns were found. In some cases
|
||||
these linters will make the needed changes directly, while in other cases you
|
||||
may need to make additional changes until ``invoke tests`` no longer reports any
|
||||
code style violations.
|
||||
|
||||
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, **make sure you have both ``en_EN.utf8`` and
|
||||
``fr_FR.utf8`` locales installed**, and then run the following two commands::
|
||||
functional tests. To do so, **make sure you have both** ``en_EN.utf8`` **and**
|
||||
``fr_FR.utf8`` **locales installed**, and then run the following command::
|
||||
|
||||
$ 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/
|
||||
invoke update-functional-tests
|
||||
|
||||
Testing on Python 2 and 3
|
||||
-------------------------
|
||||
You may also find that some tests are skipped because some dependency (e.g.,
|
||||
Pandoc) is not installed. This does not automatically mean that these tests
|
||||
have passed; you should at least verify that any skipped tests are not affected
|
||||
by your changes.
|
||||
|
||||
Testing on Python 3 currently requires some extra steps: installing
|
||||
Python 3-compatible versions of dependent packages and plugins.
|
||||
You should run the test suite under each of the supported versions of Python.
|
||||
This is best done by creating a separate Python environment for each version.
|
||||
Tox_ is a useful tool to automate running tests inside ``virtualenv``
|
||||
environments.
|
||||
|
||||
Tox_ is a useful tool to run tests on both versions. It will install the
|
||||
Python 3-compatible version of dependent packages.
|
||||
.. _Tox: https://tox.readthedocs.io/en/latest/
|
||||
|
||||
.. _Tox: http://testrun.org/tox/latest/
|
||||
Building the docs
|
||||
-----------------
|
||||
|
||||
Python 3 development tips
|
||||
=========================
|
||||
If you make changes to the documentation, you should build and inspect your
|
||||
changes before committing them::
|
||||
|
||||
Here are some tips that may be useful when doing some code for both Python 2.7
|
||||
and Python 3 at the same time:
|
||||
invoke docserve
|
||||
|
||||
- Assume every string and literal is unicode (import unicode_literals):
|
||||
Open http://localhost:8000 in your browser to review the documentation. While
|
||||
the above task is running, any changes you make and save to the documentation
|
||||
should automatically appear in the browser, as it live-reloads when it detects
|
||||
changes to the documentation source files.
|
||||
|
||||
- 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.
|
||||
Plugin development
|
||||
------------------
|
||||
|
||||
- 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.:
|
||||
To create a *new* Pelican plugin, please refer to the `plugin template`_
|
||||
repository for detailed instructions.
|
||||
|
||||
- ``isinstance(.., basestring) -> isinstance(.., six.string_types)``
|
||||
- ``isinstance(.., unicode) -> isinstance(.., six.text_type)``
|
||||
If you want to contribute to an *existing* Pelican plugin, follow the steps
|
||||
above to set up Pelican for local development, and then create a directory to
|
||||
store cloned plugin repositories::
|
||||
|
||||
- ``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.
|
||||
mkdir -p ~/projects/pelican-plugins
|
||||
|
||||
- Kept range() almost everywhere as-is (2to3 suggests list(range())), just
|
||||
changed it where I felt necessary.
|
||||
Assuming you wanted to contribute to the Simple Footnotes plugin, you would
|
||||
first browse to the `Simple Footnotes`_ repository on GitHub and tap the **Fork**
|
||||
button at top-right. Then clone the source for your fork and add the upstream
|
||||
project as a Git remote::
|
||||
|
||||
- Changed xrange() back to range(), so it is valid in both Python versions.
|
||||
git clone https://github.com/YOUR_USERNAME/simple-footnotes.git ~/projects/pelican-plugins/simple-footnotes
|
||||
cd ~/projects/pelican-plugins/simple-footnotes
|
||||
git remote add upstream https://github.com/pelican-plugins/simple-footnotes.git
|
||||
|
||||
Install the needed dependencies and set up the project::
|
||||
|
||||
invoke setup
|
||||
|
||||
Create a topic branch for your plugin bug fix or feature::
|
||||
|
||||
git checkout -b name-of-your-bugfix-or-feature
|
||||
|
||||
After writing new tests for your plugin changes, run the plugin test suite::
|
||||
|
||||
invoke tests
|
||||
|
||||
.. _plugin template: https://github.com/getpelican/cookiecutter-pelican-plugin
|
||||
.. _Simple Footnotes: https://github.com/pelican-plugins/simple-footnotes
|
||||
|
||||
Submitting your changes
|
||||
-----------------------
|
||||
|
||||
Assuming linting validation and tests pass, add a ``RELEASE.md`` file in the root
|
||||
of the project that contains the release type (major, minor, patch) and a
|
||||
summary of the changes that will be used as the release changelog entry.
|
||||
For example::
|
||||
|
||||
Release type: patch
|
||||
|
||||
Fix browser reloading upon changes to content, settings, or theme
|
||||
|
||||
Commit your changes and push your topic branch::
|
||||
|
||||
git add .
|
||||
git commit -m "Your detailed description of your changes"
|
||||
git push origin name-of-your-bugfix-or-feature
|
||||
|
||||
Finally, browse to your repository fork on GitHub and submit a pull request.
|
||||
|
||||
|
||||
Logging tips
|
||||
|
|
@ -156,8 +187,8 @@ For logging messages that are not repeated, use the usual Python way::
|
|||
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.
|
||||
arguments to logger. This is important, because the Pelican logger will
|
||||
preprocess some arguments, such as exceptions.
|
||||
|
||||
Limiting extraneous log messages
|
||||
--------------------------------
|
||||
|
|
@ -173,9 +204,9 @@ 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.
|
||||
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::
|
||||
|
||||
|
|
|
|||
119
docs/faq.rst
119
docs/faq.rst
|
|
@ -12,10 +12,10 @@ How can I help?
|
|||
===============
|
||||
|
||||
There are several ways to help out. First, you can report any Pelican
|
||||
suggestions or problems you might have via IRC (preferred) 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.
|
||||
suggestions or problems you might have via IRC (preferred) 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
|
||||
|
|
@ -35,10 +35,10 @@ via the command line. See ``pelican --help`` for more information.
|
|||
Changes to the settings 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, ensure that caching is disabled via ``LOAD_CONTENT_CACHE = False``
|
||||
or use the ``--ignore-cache`` command-line switch.
|
||||
When experimenting with different settings (especially the metadata ones)
|
||||
caching may interfere and the changes may not be visible. In such cases, ensure
|
||||
that caching is disabled via ``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?
|
||||
=========================================================================
|
||||
|
|
@ -48,7 +48,7 @@ themes to style code syntax highlighting via CSS. Specifically, you can
|
|||
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, use the style selector drop-down at top-right on the
|
||||
`Pygments project demo site <http://pygments.org/demo/>`_.
|
||||
`Pygments project demo site <https://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
|
||||
|
|
@ -70,9 +70,9 @@ I want to use Markdown, but I got an error.
|
|||
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::
|
||||
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::
|
||||
|
||||
pip install markdown
|
||||
|
||||
|
|
@ -150,9 +150,9 @@ the ``'index'`` direct template.
|
|||
What if I want to disable feed generation?
|
||||
==========================================
|
||||
|
||||
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::
|
||||
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
|
||||
|
|
@ -167,9 +167,9 @@ I'm getting a warning about feeds generated without SITEURL being set properly
|
|||
==============================================================================
|
||||
|
||||
`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 links in Pelican you will need to set ``SITEURL``
|
||||
to the full path of your site.
|
||||
<https://validator.w3.org/feed/docs/rss2.html#comments>`_. 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 links within may
|
||||
be malformed and thus the feed may not validate.
|
||||
|
|
@ -185,26 +185,26 @@ setting names). Here is an exact list of the renamed settings::
|
|||
TAG_FEED -> TAG_FEED_ATOM
|
||||
CATEGORY_FEED -> CATEGORY_FEED_ATOM
|
||||
|
||||
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::
|
||||
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
|
||||
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::
|
||||
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 = ''
|
||||
|
|
@ -212,52 +212,49 @@ You can also disable generation of tag-related pages via::
|
|||
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.
|
||||
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.
|
||||
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`.
|
||||
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``.
|
||||
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``.
|
||||
|
||||
My tag-cloud is missing/broken since I upgraded Pelican
|
||||
=======================================================
|
||||
|
||||
In an ongoing effort to steamline Pelican, `tag_cloud` generation has been
|
||||
moved out of the pelican core and into a separate `plugin
|
||||
<https://github.com/getpelican/pelican-plugins/tree/master/tag_cloud>`_.
|
||||
See the :ref:`plugins` documentation further information about the
|
||||
Pelican plugin system.
|
||||
<https://github.com/getpelican/pelican-plugins/tree/master/tag_cloud>`_. See
|
||||
the :ref:`plugins` documentation further information about the Pelican plugin
|
||||
system.
|
||||
|
||||
Since I upgraded Pelican my pages are no longer rendered
|
||||
========================================================
|
||||
|
||||
Pages were available to themes as lowercase ``pages`` and uppercase
|
||||
``PAGES``. To bring this inline with the :ref:`templates-variables` section,
|
||||
``PAGES`` has been removed. This is quickly resolved by updating your theme
|
||||
to iterate over ``pages`` instead of ``PAGES``. Just replace::
|
||||
Pages were available to themes as lowercase ``pages`` and uppercase ``PAGES``.
|
||||
To bring this inline with the :ref:`templates-variables` section, ``PAGES`` has
|
||||
been removed. This is quickly resolved by updating your theme to iterate over
|
||||
``pages`` instead of ``PAGES``. Just replace::
|
||||
|
||||
{% for pg in PAGES %}
|
||||
|
||||
|
|
@ -271,8 +268,8 @@ How can I stop Pelican from trying to parse my static files as content?
|
|||
Pelican's article and page generators run before it's static generator. That
|
||||
means if you use a setup similar to the default configuration, where a static
|
||||
source directory is defined inside a ``*_PATHS`` setting, all files that have a
|
||||
valid content file ending (``.html``, ``.rst``, ``.md``, ...) will be treated as
|
||||
articles or pages before they get treated as static files.
|
||||
valid content file ending (``.html``, ``.rst``, ``.md``, ...) will be treated
|
||||
as articles or pages before they get treated as static files.
|
||||
|
||||
To circumvent this issue either use the appropriate ``*_EXCLUDES`` setting or
|
||||
disable the offending reader via ``READERS`` if you don't need it.
|
||||
|
|
|
|||
|
|
@ -9,30 +9,38 @@ Description
|
|||
``pelican-import`` is a command-line tool for converting articles from other
|
||||
software to reStructuredText or Markdown. The supported import formats are:
|
||||
|
||||
- WordPress XML export
|
||||
- Blogger XML export
|
||||
- Dotclear export
|
||||
- Posterous API
|
||||
- Tumblr API
|
||||
- WordPress XML export
|
||||
- RSS/Atom feed
|
||||
|
||||
The conversion from HTML to reStructuredText or Markdown relies on `Pandoc`_.
|
||||
For Dotclear, if the source posts are written with Markdown syntax, they will
|
||||
not be converted (as Pelican also supports Markdown).
|
||||
|
||||
.. note::
|
||||
|
||||
Unlike Pelican, Wordpress supports multiple categories per article. These
|
||||
are imported as a comma-separated string. You have to resolve these
|
||||
manually, or use a plugin that enables multiple categories per article
|
||||
(like `more_categories`_).
|
||||
|
||||
Dependencies
|
||||
============
|
||||
|
||||
``pelican-import`` has some dependencies not required by the rest of Pelican:
|
||||
|
||||
- *BeautifulSoup4* and *lxml*, for WordPress and Dotclear import. Can be installed like
|
||||
any other Python package (``pip install BeautifulSoup4 lxml``).
|
||||
- *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.
|
||||
|
||||
.. _Pandoc: http://johnmacfarlane.net/pandoc/
|
||||
.. _Pandoc site: http://johnmacfarlane.net/pandoc/installing.html
|
||||
.. _Pandoc: https://pandoc.org/
|
||||
.. _Pandoc site: https://pandoc.org/installing.html
|
||||
|
||||
|
||||
Usage
|
||||
|
|
@ -40,9 +48,9 @@ Usage
|
|||
|
||||
::
|
||||
|
||||
pelican-import [-h] [--wpfile] [--dotclear] [--posterous] [--tumblr] [--feed] [-o OUTPUT]
|
||||
[-m MARKUP] [--dir-cat] [--dir-page] [--strip-raw] [--disable-slugs]
|
||||
[-e EMAIL] [-p PASSWORD] [-b BLOGNAME]
|
||||
pelican-import [-h] [--blogger] [--dotclear] [--posterous] [--tumblr] [--wpfile] [--feed]
|
||||
[-o OUTPUT] [-m MARKUP] [--dir-cat] [--dir-page] [--strip-raw] [--wp-custpost]
|
||||
[--wp-attach] [--disable-slugs] [-e EMAIL] [-p PASSWORD] [-b BLOGNAME]
|
||||
input|api_token|api_key
|
||||
|
||||
Positional arguments
|
||||
|
|
@ -50,31 +58,45 @@ 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
|
||||
``api_key`` (Tumblr only) api_key can be obtained from https://www.tumblr.com/oauth/apps
|
||||
============= ============================================================================
|
||||
|
||||
Optional arguments
|
||||
------------------
|
||||
|
||||
-h, --help Show this help message and exit
|
||||
--wpfile WordPress XML export (default: False)
|
||||
--blogger Blogger XML export (default: False)
|
||||
--dotclear Dotclear export (default: False)
|
||||
--posterous Posterous API (default: False)
|
||||
--tumblr Tumblr API (default: False)
|
||||
--wpfile WordPress XML export (default: False)
|
||||
--feed Feed to parse (default: False)
|
||||
-o OUTPUT, --output OUTPUT
|
||||
Output path (default: output)
|
||||
Output path (default: content)
|
||||
-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.
|
||||
directory (blogger and wordpress import only)
|
||||
(default: False)
|
||||
--filter-author Import only post from the specified author
|
||||
--strip-raw Strip raw HTML code that can't be converted to markup
|
||||
such as flash embeds or iframes (wordpress import
|
||||
only) (default: False)
|
||||
--wp-custpost Put wordpress custom post types in directories. If
|
||||
used with --dir-cat option directories will be created
|
||||
as "/post_type/category/" (wordpress import only)
|
||||
--wp-attach Download files uploaded to wordpress as attachments.
|
||||
Files will be added to posts as a list in the post
|
||||
header and links to the files within the post will be
|
||||
updated. All files will be downloaded, even if they
|
||||
aren't associated with a post. Files will be downloaded
|
||||
with their original path inside the output directory,
|
||||
e.g. "output/wp-uploads/date/postname/file.jpg".
|
||||
(wordpress import only) (requires an internet
|
||||
connection)
|
||||
--disable-slugs Disable storing slugs from imported posts within
|
||||
output. With this disabled, your Pelican URLs may not
|
||||
be consistent with your original posts. (default:
|
||||
|
|
@ -90,9 +112,9 @@ Optional arguments
|
|||
Examples
|
||||
========
|
||||
|
||||
For WordPress::
|
||||
For Blogger::
|
||||
|
||||
$ pelican-import --wpfile -o ~/output ~/posts.xml
|
||||
$ pelican-import --blogger -o ~/output ~/posts.xml
|
||||
|
||||
For Dotclear::
|
||||
|
||||
|
|
@ -106,10 +128,16 @@ For Tumblr::
|
|||
|
||||
$ pelican-import --tumblr -o ~/output --blogname=<blogname> <api_token>
|
||||
|
||||
For WordPress::
|
||||
|
||||
$ pelican-import --wpfile -o ~/output ~/posts.xml
|
||||
|
||||
Tests
|
||||
=====
|
||||
|
||||
To test the module, one can use sample files:
|
||||
|
||||
- for WordPress: http://wpcandy.com/made/the-sample-post-collection
|
||||
- for WordPress: https://www.wpbeginner.com/wp-themes/how-to-add-dummy-content-for-theme-development-in-wordpress/
|
||||
- for Dotclear: http://media.dotaddict.org/tda/downloads/lorem-backup.txt
|
||||
|
||||
.. _more_categories: https://github.com/getpelican/pelican-plugins/tree/master/more_categories
|
||||
|
|
@ -6,14 +6,14 @@ Pelican |release|
|
|||
|
||||
.. warning::
|
||||
|
||||
This documentation is for the version of Pelican currently under development.
|
||||
Were you looking for version |last_stable| documentation?
|
||||
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
|
||||
* 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
|
||||
|
|
@ -76,10 +76,10 @@ Documentation
|
|||
|
||||
.. Links
|
||||
|
||||
.. _Python: http://www.python.org/
|
||||
.. _Python: https://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/latest/
|
||||
.. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html
|
||||
.. _Markdown: https://daringfireball.net/projects/markdown/
|
||||
.. _Jinja2: https://palletsprojects.com/p/jinja/
|
||||
.. _`Pelican documentation`: https://docs.getpelican.com/latest/
|
||||
.. _`Pelican's internals`: https://docs.getpelican.com/en/latest/internals.html
|
||||
.. _`Pelican plugins`: https://github.com/getpelican/pelican-plugins
|
||||
|
|
|
|||
|
|
@ -1,19 +1,23 @@
|
|||
Installing Pelican
|
||||
##################
|
||||
|
||||
Pelican currently runs best on Python 2.7.x and 3.3+; earlier versions of
|
||||
Pelican currently runs best on Python 2.7.x and 3.5+; earlier versions of
|
||||
Python are not supported.
|
||||
|
||||
You can install Pelican via several different methods. The simplest is via
|
||||
`pip <http://www.pip-installer.org/>`_::
|
||||
`pip <https://pip.pypa.io/en/stable/>`_::
|
||||
|
||||
pip install pelican
|
||||
|
||||
Or, if you plan on using Markdown::
|
||||
|
||||
pip install pelican[Markdown]
|
||||
|
||||
(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.
|
||||
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::
|
||||
|
||||
|
|
@ -22,9 +26,8 @@ session and create a new virtual environment for Pelican::
|
|||
source bin/activate
|
||||
|
||||
Once the virtual environment has been created and activated, Pelican can be
|
||||
installed via ``pip install pelican`` as noted above. Alternatively, if
|
||||
you have the project source, you can install Pelican using the distutils
|
||||
method::
|
||||
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
|
||||
|
|
@ -40,13 +43,17 @@ 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::
|
||||
If you plan on using `Markdown <https://pypi.org/project/Markdown/>`_ as a
|
||||
markup format, you can install Pelican with Markdown support::
|
||||
|
||||
pip install pelican[Markdown]
|
||||
|
||||
Or you might need to install it a posteriori::
|
||||
|
||||
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
|
||||
requisite `Typogrify <https://pypi.org/project/typogrify/>`_ library must be
|
||||
installed::
|
||||
|
||||
pip install typogrify
|
||||
|
|
@ -57,22 +64,22 @@ 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
|
||||
* `feedgenerator <https://pypi.org/project/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
|
||||
* `jinja2 <https://pypi.org/project/Jinja2/>`_, for templating support
|
||||
* `pygments <https://pypi.org/project/Pygments/>`_, for syntax highlighting
|
||||
* `docutils <https://pypi.org/project/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
|
||||
* `pytz <https://pypi.org/project/pytz/>`_, for timezone definitions
|
||||
* `blinker <https://pypi.org/project/blinker/>`_, an object-to-object and
|
||||
broadcast signaling system
|
||||
* `unidecode <http://pypi.python.org/pypi/Unidecode>`_, for ASCII
|
||||
* `unidecode <https://pypi.org/project/Unidecode/>`_, for ASCII
|
||||
transliterations of Unicode text
|
||||
* `six <http://pypi.python.org/pypi/six>`_, for Python 2 and 3 compatibility
|
||||
* `six <https://pypi.org/project/six/>`_, for Python 2 and 3 compatibility
|
||||
utilities
|
||||
* `MarkupSafe <http://pypi.python.org/pypi/MarkupSafe>`_, for a markup safe
|
||||
* `MarkupSafe <https://pypi.org/project/MarkupSafe/>`_, for a markup safe
|
||||
string implementation
|
||||
* `python-dateutil <https://pypi.python.org/pypi/python-dateutil>`_, to read
|
||||
* `python-dateutil <https://pypi.org/project/python-dateutil/>`_, to read
|
||||
the date metadata
|
||||
|
||||
Upgrading
|
||||
|
|
@ -95,16 +102,23 @@ your site::
|
|||
|
||||
pelican-quickstart
|
||||
|
||||
If run inside an activated virtual environment, ``pelican-quickstart`` will
|
||||
look for an associated project path inside ``$VIRTUAL_ENV/.project``. If that
|
||||
file exists and contains a valid directory path, the new Pelican project will
|
||||
be saved at that location. Otherwise, the default is the current working
|
||||
directory. To set the new project path on initial invocation, use:
|
||||
``pelican-quickstart --path /your/desired/directory``
|
||||
|
||||
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)::
|
||||
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)
|
||||
│ └── (pages)
|
||||
├── output
|
||||
├── develop_server.sh
|
||||
├── fabfile.py
|
||||
├── tasks.py
|
||||
├── Makefile
|
||||
├── pelicanconf.py # Main settings file
|
||||
└── publishconf.py # Settings to use when ready to publish
|
||||
|
|
@ -112,4 +126,4 @@ can optionally add yourself if you plan to create non-chronological content)::
|
|||
The next step is to begin to adding content to the *content* folder that has
|
||||
been created for you.
|
||||
|
||||
.. _virtualenv: http://www.virtualenv.org/
|
||||
.. _virtualenv: https://virtualenv.pypa.io/en/latest/
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
Pelican internals
|
||||
#################
|
||||
|
||||
This section describe how Pelican works internally. As you'll see, it's
|
||||
quite simple, but a bit of documentation doesn't hurt. :)
|
||||
This section describe how Pelican works internally. As you'll see, it's quite
|
||||
simple, but a bit of documentation doesn't hurt. :)
|
||||
|
||||
You can also find in the :doc:`report` section an excerpt of a report the
|
||||
original author wrote with some software design information.
|
||||
|
|
@ -13,9 +13,8 @@ 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
|
||||
files, and the output is a blog, but both input and output can be anything you
|
||||
want.
|
||||
output. Usually, the input files are reStructuredText and Markdown files, and
|
||||
the output is a blog, but both input and output can be anything you want.
|
||||
|
||||
The logic is separated into different classes and concepts:
|
||||
|
||||
|
|
@ -27,20 +26,21 @@ The logic is separated into different classes and concepts:
|
|||
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
|
||||
whatever they want. Most of the time, it's generating files from inputs.
|
||||
* **Generators** generate the different outputs. For instance, Pelican comes
|
||||
with ``ArticlesGenerator`` and ``PageGenerator``. Given a configuration, they
|
||||
can do whatever they want. Most of the time, it's generating files from
|
||||
inputs.
|
||||
|
||||
* Pelican also uses templates, so it's easy to write your own theme. The
|
||||
syntax is `Jinja2 <http://jinja.pocoo.org/>`_ and is very easy to learn, so
|
||||
syntax is `Jinja2 <https://palletsprojects.com/p/jinja/>`_ and is very easy to learn, so
|
||||
don't hesitate to jump in and build your own theme.
|
||||
|
||||
How to implement a new reader?
|
||||
==============================
|
||||
|
||||
Is there an awesome markup language you want to add to Pelican?
|
||||
Well, the only thing you have to do is to create a class with a ``read``
|
||||
method that returns HTML content and some metadata.
|
||||
Is there an awesome markup language you want to add to Pelican? Well, the only
|
||||
thing you have to do is to create a class with a ``read`` method that returns
|
||||
HTML content and some metadata.
|
||||
|
||||
Take a look at the Markdown reader::
|
||||
|
||||
|
|
@ -65,17 +65,17 @@ Take a look at the Markdown reader::
|
|||
|
||||
Simple, isn't it?
|
||||
|
||||
If your new reader requires additional Python dependencies, then you should wrap
|
||||
their ``import`` statements in a ``try...except`` block. Then inside the reader's
|
||||
class, set the ``enabled`` class attribute to mark import success or failure.
|
||||
This makes it possible for users to continue using their favourite markup method
|
||||
without needing to install modules for formats they don't use.
|
||||
If your new reader requires additional Python dependencies, then you should
|
||||
wrap their ``import`` statements in a ``try...except`` block. Then inside the
|
||||
reader's class, set the ``enabled`` class attribute to mark import success or
|
||||
failure. This makes it possible for users to continue using their favourite
|
||||
markup method without needing to install modules for formats they don't use.
|
||||
|
||||
How to implement a new generator?
|
||||
=================================
|
||||
|
||||
Generators have two important methods. You're not forced to create
|
||||
both; only the existing ones will be called.
|
||||
Generators have two important methods. You're not forced to create both; only
|
||||
the existing ones will be called.
|
||||
|
||||
* ``generate_context``, that is called first, for all the generators.
|
||||
Do whatever you have to do, and update the global context if needed. This
|
||||
|
|
@ -89,5 +89,5 @@ both; only the existing ones will be called.
|
|||
generating the output. :) It's here that you may want to look at the context
|
||||
and call the methods of the ``writer`` object that is passed as the first
|
||||
argument of this function. In the ``PageGenerator`` example, this method will
|
||||
look at all the pages recorded in the global context and output a file on
|
||||
the disk (using the writer method ``write_file``) for each page encountered.
|
||||
look at all the pages recorded in the global context and output a file on the
|
||||
disk (using the writer method ``write_file``) for each page encountered.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ pelican-themes
|
|||
Description
|
||||
===========
|
||||
|
||||
``pelican-themes`` is a command line tool for managing themes for Pelican.
|
||||
``pelican-themes`` is a command line tool for managing themes for Pelican. See
|
||||
:ref:`settings/themes` for settings related to themes.
|
||||
|
||||
|
||||
Usage
|
||||
|
|
@ -44,7 +45,8 @@ Examples
|
|||
Listing the installed themes
|
||||
""""""""""""""""""""""""""""
|
||||
|
||||
With ``pelican-themes``, you can see the available themes by using the ``-l`` or ``--list`` option:
|
||||
With ``pelican-themes``, you can see the available themes by using the ``-l``
|
||||
or ``--list`` option:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
|
|
@ -57,11 +59,15 @@ With ``pelican-themes``, you can see the available themes by using the ``-l`` or
|
|||
two-column@
|
||||
simple
|
||||
|
||||
In this example, we can see there are three themes available: ``notmyidea``, ``simple``, and ``two-column``.
|
||||
In this example, we can see there are three themes available: ``notmyidea``,
|
||||
``simple``, and ``two-column``.
|
||||
|
||||
``two-column`` is prefixed with an ``@`` because this theme is not copied to the Pelican theme path, but is instead just linked to it (see `Creating symbolic links`_ for details about creating symbolic links).
|
||||
``two-column`` is prefixed with an ``@`` because this theme is not copied to
|
||||
the Pelican theme path, but is instead just linked to it (see `Creating
|
||||
symbolic links`_ for details about creating symbolic links).
|
||||
|
||||
Note that you can combine the ``--list`` option with the ``-v`` or ``--verbose`` option to get more verbose output, like this:
|
||||
Note that you can combine the ``--list`` option with the ``-v`` or
|
||||
``--verbose`` option to get more verbose output, like this:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
|
|
@ -75,7 +81,8 @@ Installing themes
|
|||
"""""""""""""""""
|
||||
|
||||
You can install one or more themes using the ``-i`` or ``--install`` option.
|
||||
This option takes as argument the path(s) of the theme(s) you want to install, and can be combined with the verbose option:
|
||||
This option takes as argument the path(s) of the theme(s) you want to install,
|
||||
and can be combined with the verbose option:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
|
|
@ -95,8 +102,9 @@ This option takes as argument the path(s) of the theme(s) you want to install, a
|
|||
Removing themes
|
||||
"""""""""""""""
|
||||
|
||||
The ``pelican-themes`` command can also remove themes from the Pelican themes path.
|
||||
The ``-r`` or ``--remove`` option takes as argument the name(s) of the theme(s) you want to remove, and can be combined with the ``--verbose`` option.
|
||||
The ``pelican-themes`` command can also remove themes from the Pelican themes
|
||||
path. The ``-r`` or ``--remove`` option takes as argument the name(s) of the
|
||||
theme(s) you want to remove, and can be combined with the ``--verbose`` option.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
|
|
@ -113,15 +121,19 @@ The ``-r`` or ``--remove`` option takes as argument the name(s) of the theme(s)
|
|||
Creating symbolic links
|
||||
"""""""""""""""""""""""
|
||||
|
||||
``pelican-themes`` can also install themes by creating symbolic links instead of copying entire themes into the Pelican themes path.
|
||||
``pelican-themes`` can also install themes by creating symbolic links instead
|
||||
of copying entire themes into the Pelican themes path.
|
||||
|
||||
To symbolically link a theme, you can use the ``-s`` or ``--symlink``, which works exactly as the ``--install`` option:
|
||||
To symbolically link a theme, you can use the ``-s`` or ``--symlink``, which
|
||||
works exactly as the ``--install`` option:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# pelican-themes --symlink ~/Dev/Python/pelican-themes/two-column
|
||||
|
||||
In this example, the ``two-column`` theme is now symbolically linked to the Pelican themes path, so we can use it, but we can also modify it without having to reinstall it after each modification.
|
||||
In this example, the ``two-column`` theme is now symbolically linked to the
|
||||
Pelican themes path, so we can use it, but we can also modify it without having
|
||||
to reinstall it after each modification.
|
||||
|
||||
This is useful for theme development:
|
||||
|
||||
|
|
@ -142,7 +154,9 @@ This is useful for theme development:
|
|||
Doing several things at once
|
||||
""""""""""""""""""""""""""""
|
||||
|
||||
The ``--install``, ``--remove`` and ``--symlink`` option are not mutually exclusive, so you can combine them in the same command line to do more than one operation at time, like this:
|
||||
The ``--install``, ``--remove`` and ``--symlink`` option are not mutually
|
||||
exclusive, so you can combine them in the same command line to do more than one
|
||||
operation at time, like this:
|
||||
|
||||
|
||||
.. code-block:: console
|
||||
|
|
@ -152,4 +166,5 @@ The ``--install``, ``--remove`` and ``--symlink`` option are not mutually exclus
|
|||
--symlink ~/Dev/Python/pelican-themes/two-column \
|
||||
--verbose
|
||||
|
||||
In this example, the theme ``notmyidea-cms`` is replaced by the theme ``notmyidea-cms-fr``
|
||||
In this example, the theme ``notmyidea-cms`` is replaced by the theme
|
||||
``notmyidea-cms-fr``
|
||||
|
|
|
|||
101
docs/plugins.rst
101
docs/plugins.rst
|
|
@ -9,24 +9,38 @@ 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. There are two
|
||||
ways to do so. The first method is to specify strings with the path to the
|
||||
callables::
|
||||
Starting with version 5.0, Pelican moved to a new plugin structure utilizing
|
||||
namespace packages. Plugins supporting this structure will install under the
|
||||
namespace package ``pelican.plugins`` and can be automatically discovered
|
||||
by Pelican.
|
||||
|
||||
PLUGINS = ['package.myplugin',]
|
||||
If you leave the ``PLUGINS`` setting as default (``None``), Pelican will then
|
||||
collect the namespace plugins and register them. If on the other hand you
|
||||
specify a ``PLUGINS`` settings as a list of plugins, this autodiscovery will
|
||||
be disabled and only listed plugins will be registered and you will have to
|
||||
explicitly list the namespace plugins as well.
|
||||
|
||||
Alternatively, another method is to import them and add them to the list::
|
||||
If you are using ``PLUGINS`` setting, you can specify plugins in two ways.
|
||||
The first method specifies plugins as a list of strings. Namespace plugins can
|
||||
be specified either by their full names (``pelican.plugins.myplugin``) or by
|
||||
their short names (``myplugin``)::
|
||||
|
||||
PLUGINS = ['package.myplugin',
|
||||
'namespace_plugin1',
|
||||
'pelican.plugins.namespace_plugin2']
|
||||
|
||||
Alternatively, you can import them in your settings file and pass the modules::
|
||||
|
||||
from package import myplugin
|
||||
PLUGINS = [myplugin,]
|
||||
from pelican.plugins import namespace_plugin1, namespace_plugin2
|
||||
PLUGINS = [myplugin, namespace_plugin1, namespace_plugin2]
|
||||
|
||||
.. note::
|
||||
|
||||
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.
|
||||
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
|
||||
|
|
@ -37,11 +51,13 @@ the ``PLUGIN_PATHS`` list can be absolute or relative to the settings file::
|
|||
|
||||
Where to find plugins
|
||||
=====================
|
||||
Namespace plugins can be found in the `pelican-plugins organization`_ as
|
||||
individual repositories. Legacy plugins are collected in the `pelican-plugins
|
||||
repository`_ and they will be slowly phased out in favor of the namespace
|
||||
versions.
|
||||
|
||||
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
|
||||
.. _pelican-plugins organization: https://github.com/pelican-plugins
|
||||
.. _pelican-plugins repository: 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
|
||||
|
|
@ -60,7 +76,7 @@ which you map the signals to your plugin logic. Let's take a simple example::
|
|||
from pelican import signals
|
||||
|
||||
def test(sender):
|
||||
print "%s initialized !!" % sender
|
||||
print("{} initialized !!".format(sender))
|
||||
|
||||
def register():
|
||||
signals.initialized.connect(test)
|
||||
|
|
@ -71,6 +87,33 @@ which you map the signals to your plugin logic. Let's take a simple example::
|
|||
your ``register`` callable or they will be garbage-collected before the
|
||||
signal is emitted.
|
||||
|
||||
Namespace plugin structure
|
||||
--------------------------
|
||||
|
||||
Namespace plugins must adhere to a certain structure in order to function
|
||||
properly. They need to be installable (i.e. contain ``setup.py`` or equivalent)
|
||||
and have a folder structure as follows::
|
||||
|
||||
myplugin
|
||||
├── pelican
|
||||
│ └── plugins
|
||||
│ └── myplugin
|
||||
│ ├── __init__.py
|
||||
│ └── ...
|
||||
├── ...
|
||||
└── setup.py
|
||||
|
||||
It is crucial that ``pelican`` or ``pelican/plugins`` folder **not**
|
||||
contain an ``__init__.py`` file. In fact, it is best to have those folders
|
||||
empty besides the listed folders in the above structure and keep your
|
||||
plugin related files contained solely in the ``pelican/plugins/myplugin``
|
||||
folder to avoid any issues.
|
||||
|
||||
For easily setting up the proper structure, a `cookiecutter template for
|
||||
plugins`_ is provided. Refer to the README in the link for how to use it.
|
||||
|
||||
.. _cookiecutter template for plugins: https://github.com/getpelican/cookiecutter-pelican-plugin
|
||||
|
||||
List of signals
|
||||
===============
|
||||
|
||||
|
|
@ -108,6 +151,7 @@ page_generator_preread page_generator invoked befor
|
|||
use if code needs to do something before every page is parsed.
|
||||
page_generator_init page_generator invoked in the PagesGenerator.__init__
|
||||
page_generator_finalized page_generator invoked at the end of PagesGenerator.generate_context
|
||||
page_generator_write_page page_generator, content invoked before writing each page, the page is passed as content
|
||||
page_writer_finalized page_generator, writer invoked after all pages have been written, but before the page generator
|
||||
is closed.
|
||||
static_generator_context static_generator, metadata
|
||||
|
|
@ -125,17 +169,17 @@ feed_written path, context, feed invoked each
|
|||
|
||||
.. warning::
|
||||
|
||||
Avoid ``content_object_init`` signal if you intend to read ``summary``
|
||||
or ``content`` properties of the content object. That combination can
|
||||
result in unresolved links when :ref:`ref-linking-to-internal-content`
|
||||
(see `pelican-plugins bug #314`_). Use ``_summary`` and ``_content``
|
||||
properties instead, or, alternatively, run your plugin at a later
|
||||
stage (e.g. ``all_generators_finalized``).
|
||||
Avoid ``content_object_init`` signal if you intend to read ``summary`` or
|
||||
``content`` properties of the content object. That combination can result in
|
||||
unresolved links when :ref:`ref-linking-to-internal-content` (see
|
||||
`pelican-plugins bug #314`_). Use ``_summary`` and ``_content`` properties
|
||||
instead, or, alternatively, run your plugin at a later stage (e.g.
|
||||
``all_generators_finalized``).
|
||||
|
||||
.. note::
|
||||
|
||||
After Pelican 3.2, signal names were standardized. Older plugins
|
||||
may need to be updated to use the new names:
|
||||
After Pelican 3.2, signal names were standardized. Older plugins may need
|
||||
to be updated to use the new names:
|
||||
|
||||
========================== ===========================
|
||||
Old name New name
|
||||
|
|
@ -161,9 +205,9 @@ 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.
|
||||
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.
|
||||
|
|
@ -215,6 +259,7 @@ Adding a new generator is also really easy. You might want to have a look at
|
|||
# define a new generator here if you need to
|
||||
return MyGenerator
|
||||
|
||||
signals.get_generators.connect(get_generators)
|
||||
def register():
|
||||
signals.get_generators.connect(get_generators)
|
||||
|
||||
.. _pelican-plugins bug #314: https://github.com/getpelican/pelican-plugins/issues/314
|
||||
|
|
|
|||
135
docs/publish.rst
135
docs/publish.rst
|
|
@ -25,14 +25,19 @@ argument, like so::
|
|||
pelican --write-selected output/posts/my-post-title.html
|
||||
|
||||
Note that you must specify the path to the generated *output* file — not the
|
||||
source content. To determine the output file path, use the ``--debug`` flag to
|
||||
determine the correct file name and location. If desired, ``--write-selected``
|
||||
can take a comma-separated list of paths or can be configured as a setting.
|
||||
(See: :ref:`writing_only_selected_content`)
|
||||
source content. To determine the output file name and location, use the
|
||||
``--debug`` flag. If desired, ``--write-selected`` can take a comma-separated
|
||||
list of paths or can be configured as a setting. (See:
|
||||
:ref:`writing_only_selected_content`)
|
||||
|
||||
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.
|
||||
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. On non-Windows
|
||||
environments, this option can also be combined with the ``-l`` or ``--listen``
|
||||
option to simultaneously both auto-regenerate *and* serve the output at
|
||||
http://localhost:8000::
|
||||
|
||||
pelican --autoreload --listen
|
||||
|
||||
Pelican has other command-line switches available. Have a look at the help to
|
||||
see all the options you can use::
|
||||
|
|
@ -49,20 +54,12 @@ 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.
|
||||
assets, running Pelican's simple built-in web server will often provide a more
|
||||
reliable previewing experience::
|
||||
|
||||
For Python 2, run::
|
||||
pelican --listen
|
||||
|
||||
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
|
||||
Once the web server has been started, you can preview your site at:
|
||||
http://localhost:8000/
|
||||
|
||||
Deployment
|
||||
|
|
@ -75,17 +72,17 @@ feeds, etc.) that you may have defined::
|
|||
|
||||
pelican content -s publishconf.py
|
||||
|
||||
To base your publish configuration on top of your ``pelicanconf.py``, you
|
||||
can import your ``pelicanconf`` settings by including the following line in
|
||||
your ``publishconf.py``::
|
||||
To base your publish configuration on top of your ``pelicanconf.py``, you can
|
||||
import your ``pelicanconf`` settings by including the following line in your
|
||||
``publishconf.py``::
|
||||
|
||||
from pelicanconf import *
|
||||
|
||||
If you have generated a ``publishconf.py`` using ``pelican-quickstart``,
|
||||
this line is included by default.
|
||||
If you have generated a ``publishconf.py`` using ``pelican-quickstart``, this
|
||||
line is included by default.
|
||||
|
||||
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
|
||||
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/
|
||||
|
|
@ -98,81 +95,75 @@ 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.
|
||||
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 ``tasks.py`` and ``Makefile`` will be
|
||||
generated in the root of your project. These files, pre-populated with certain
|
||||
information gleaned from other answers provided during the
|
||||
``pelican-quickstart`` process, are meant as a starting point and 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
|
||||
Invoke
|
||||
------
|
||||
|
||||
The advantage of Fabric_ is that it is written in Python and thus can be used
|
||||
The advantage of Invoke_ is that it is written in Python and thus can be used
|
||||
in a wide range of environments. The downside is that it must be installed
|
||||
separately. Use the following command to install Fabric, prefixing with
|
||||
separately. Use the following command to install Invoke, prefixing with
|
||||
``sudo`` if your environment requires it::
|
||||
|
||||
pip install Fabric
|
||||
pip install invoke
|
||||
|
||||
.. note:: Installing PyCrypto on Windows
|
||||
|
||||
Fabric depends upon PyCrypto_, which is tricky to install
|
||||
if your system doesn't have a C compiler.
|
||||
For Windows users, before installing Fabric, use
|
||||
``easy_install http://www.voidspace.org.uk/downloads/pycrypto26/pycrypto-2.6.win32-py2.7.exe``
|
||||
per this `StackOverflow suggestion <http://stackoverflow.com/a/11405769/6364>`_
|
||||
You're more likely to have success
|
||||
with the Win32 versions of Python 2.7 and PyCrypto,
|
||||
than with the Win64—\
|
||||
even if your operating system is a 64-bit version of Windows.
|
||||
|
||||
Take a moment to open the ``fabfile.py`` file that was generated in your
|
||||
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
|
||||
Take a moment to open the ``tasks.py`` file that was generated in your project
|
||||
root. You will see a number of commands, any one of which can be renamed,
|
||||
removed, and/or customized to your liking. Using the out-of-the-box
|
||||
configuration, you can generate your site via::
|
||||
|
||||
fab build
|
||||
invoke build
|
||||
|
||||
If you'd prefer to have Pelican automatically regenerate your site every time a
|
||||
change is detected (which is handy when testing locally), use the following
|
||||
command instead::
|
||||
|
||||
fab regenerate
|
||||
invoke regenerate
|
||||
|
||||
To serve the generated site so it can be previewed in your browser at
|
||||
http://localhost:8000/::
|
||||
|
||||
fab serve
|
||||
invoke serve
|
||||
|
||||
To serve the generated site with automatic browser reloading every time a
|
||||
change is detected, first ``pip install livereload``, then use the
|
||||
following command::
|
||||
|
||||
invoke livereload
|
||||
|
||||
If during the ``pelican-quickstart`` process you answered "yes" when asked
|
||||
whether you want to upload your site via SSH, you can use the following command
|
||||
to publish your site via rsync over SSH::
|
||||
|
||||
fab publish
|
||||
invoke publish
|
||||
|
||||
These are just a few of the commands available by default, so feel free to
|
||||
explore ``fabfile.py`` and see what other commands are available. More
|
||||
importantly, don't hesitate to customize ``fabfile.py`` to suit your specific
|
||||
explore ``tasks.py`` and see what other commands are available. More
|
||||
importantly, don't hesitate to customize ``tasks.py`` to suit your specific
|
||||
needs and preferences.
|
||||
|
||||
Make
|
||||
----
|
||||
|
||||
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.
|
||||
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 using the settings in
|
||||
``pelicanconf.py``, run::
|
||||
|
|
@ -201,10 +192,7 @@ separate terminal sessions, but you can run both at once via::
|
|||
make devserver
|
||||
|
||||
The above command will simultaneously run Pelican in regeneration mode as well
|
||||
as serve the output at http://localhost:8000. Once you are done testing your
|
||||
changes, you should stop the development server via::
|
||||
|
||||
./develop_server.sh stop
|
||||
as serve the output at http://localhost:8000.
|
||||
|
||||
When you're ready to publish your site, you can upload it via the method(s) you
|
||||
chose during the ``pelican-quickstart`` questionnaire. For this example, we'll
|
||||
|
|
@ -219,5 +207,4 @@ That's it! Your site should now be live.
|
|||
executables, such as ``python3``, you can set the ``PY`` and ``PELICAN``
|
||||
environment variables, respectively, to override the default executable names.)
|
||||
|
||||
.. _Fabric: http://fabfile.org/
|
||||
.. _PyCrypto: http://pycrypto.org
|
||||
.. _Invoke: https://www.pyinvoke.org/
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ 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
|
||||
2.7.x or Python 3.5+ by running the following command in your preferred
|
||||
terminal, prefixing with ``sudo`` if permissions warrant::
|
||||
|
||||
pip install pelican markdown
|
||||
pip install pelican[Markdown]
|
||||
|
||||
Create a project
|
||||
----------------
|
||||
|
|
@ -30,7 +30,7 @@ by asking some questions about your site::
|
|||
For questions that have default values denoted in brackets, feel free to use
|
||||
the Return key to accept those default values [#tzlocal_fn]_. When asked for
|
||||
your URL prefix, enter your domain name as indicated (e.g.,
|
||||
``http://example.com``).
|
||||
``https://example.com``).
|
||||
|
||||
Create an article
|
||||
-----------------
|
||||
|
|
@ -50,27 +50,27 @@ Given that this example article is in Markdown format, save it as
|
|||
Generate your site
|
||||
------------------
|
||||
|
||||
From your site directory, run the ``pelican`` command to generate your site::
|
||||
From your project root 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.)
|
||||
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 Pelican's web server::
|
||||
Open a new terminal session, navigate to your project root directory, and
|
||||
run the following command to launch Pelican's web server::
|
||||
|
||||
cd ~/projects/yoursite/output
|
||||
python -m pelican.server
|
||||
pelican --listen
|
||||
|
||||
Preview your site by navigating to http://localhost:8000/ in your browser.
|
||||
|
||||
Continue reading the other documentation sections for more detail, and check out
|
||||
the Pelican wiki's Tutorials_ page for links to community-published tutorials.
|
||||
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
|
||||
|
||||
|
|
@ -78,5 +78,5 @@ Footnotes
|
|||
---------
|
||||
|
||||
.. [#tzlocal_fn] You can help localize default fields by installing the
|
||||
optional `tzlocal <https://pypi.python.org/pypi/tzlocal>`_
|
||||
optional `tzlocal <https://pypi.org/project/tzlocal/>`_
|
||||
module.
|
||||
|
|
|
|||
117
docs/report.rst
117
docs/report.rst
|
|
@ -7,29 +7,27 @@ Some history about Pelican
|
|||
right after writing Pelican, in December 2010. The information may not be
|
||||
up-to-date.
|
||||
|
||||
Pelican is a simple static blog generator. It parses markup files
|
||||
(Markdown or reStructuredText for now) and generates an HTML folder
|
||||
with all the files in it.
|
||||
I've chosen to use Python to implement Pelican because it seemed to
|
||||
be simple and to fit to my needs. I did not wanted to define a class for
|
||||
each thing, but still wanted to keep my things loosely coupled.
|
||||
It turns out that it was exactly what I wanted. From time to time,
|
||||
thanks to the feedback of some users, it took me a very few time to
|
||||
provide fixes on it. So far, I've re-factored the Pelican code by two
|
||||
Pelican is a simple static blog generator. It parses markup files (Markdown or
|
||||
reStructuredText for now) and generates an HTML folder with all the files in
|
||||
it. I've chosen to use Python to implement Pelican because it seemed to be
|
||||
simple and to fit to my needs. I did not wanted to define a class for each
|
||||
thing, but still wanted to keep my things loosely coupled. It turns out that it
|
||||
was exactly what I wanted. From time to time, thanks to the feedback of some
|
||||
users, it took me a very few time to provide fixes on it. So far, I've
|
||||
re-factored the Pelican code by two
|
||||
times; each time took less than 30 minutes.
|
||||
|
||||
Use case
|
||||
========
|
||||
|
||||
I was previously using WordPress, a solution you can host on a web
|
||||
server to manage your blog. Most of the time, I prefer using markup
|
||||
languages such as Markdown or reStructuredText to type my articles.
|
||||
To do so, I use vim. I think it is important to let the people choose the
|
||||
tool they want to write the articles. In my opinion, a blog manager
|
||||
should just allow you to take any kind of input and transform it to a
|
||||
weblog. That's what Pelican does.
|
||||
You can write your articles using the tool you want, and the markup
|
||||
language you want, and then generate a static HTML weblog.
|
||||
I was previously using WordPress, a solution you can host on a web server to
|
||||
manage your blog. Most of the time, I prefer using markup languages such as
|
||||
Markdown or reStructuredText to type my articles. To do so, I use vim. I think
|
||||
it is important to let the people choose the tool they want to write the
|
||||
articles. In my opinion, a blog manager should just allow you to take any kind
|
||||
of input and transform it to a weblog. That's what Pelican does. You can write
|
||||
your articles using the tool you want, and the markup language you want, and
|
||||
then generate a static HTML weblog.
|
||||
|
||||
.. image:: _static/overall.png
|
||||
|
||||
|
|
@ -40,39 +38,36 @@ Design process
|
|||
==============
|
||||
|
||||
Pelican came from a need I have. I started by creating a single file
|
||||
application, and I have make it grow to support what it does by now.
|
||||
To start, I wrote a piece of documentation about what I wanted to do.
|
||||
Then, I created the content I wanted to parse (the reStructuredText files)
|
||||
and started experimenting with the code. Pelican was 200 lines long and
|
||||
contained almost ten functions and one class when it was first usable.
|
||||
application, and I have make it grow to support what it does by now. To start,
|
||||
I wrote a piece of documentation about what I wanted to do. Then, I created the
|
||||
content I wanted to parse (the reStructuredText files) and started
|
||||
experimenting with the code. Pelican was 200 lines long and contained almost
|
||||
ten functions and one class when it was first usable.
|
||||
|
||||
I have been facing different problems all over the time and wanted to
|
||||
add features to Pelican while using it. The first change I have done was
|
||||
to add the support of a settings file. It is possible to pass the options to
|
||||
the command line, but can be tedious if there is a lot of them.
|
||||
In the same way, I have added the support of different things over
|
||||
time: Atom feeds, multiple themes, multiple markup support, etc.
|
||||
At some point, it appears that the "only one file" mantra was not good
|
||||
enough for Pelican, so I decided to rework a bit all that, and split this in
|
||||
multiple different files.
|
||||
I have been facing different problems all over the time and wanted to add
|
||||
features to Pelican while using it. The first change I have done was to add the
|
||||
support of a settings file. It is possible to pass the options to the command
|
||||
line, but can be tedious if there is a lot of them. In the same way, I have
|
||||
added the support of different things over time: Atom feeds, multiple themes,
|
||||
multiple markup support, etc. At some point, it appears that the "only one
|
||||
file" mantra was not good enough for Pelican, so I decided to rework a bit all
|
||||
that, and split this in multiple different files.
|
||||
|
||||
I’ve separated the logic in different classes and concepts:
|
||||
|
||||
* *writers* are responsible of all the writing process of the files.
|
||||
They are responsible of writing .html files, RSS feeds and so on.
|
||||
Since those operations are commonly used, the object is created
|
||||
once, and then passed to the generators.
|
||||
They are responsible of writing .html files, RSS feeds and so on. Since those
|
||||
operations are commonly used, the object is created once, and then passed to
|
||||
the generators.
|
||||
|
||||
* *readers* are used to read from various formats (Markdown and
|
||||
reStructuredText for now, but the system is extensible). Given a
|
||||
file, they return metadata (author, tags, category, etc) and
|
||||
content (HTML formatted).
|
||||
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 an ArticlesGenerator and PagesGenerator, into
|
||||
others. Given a configuration, they can do whatever you want
|
||||
them to do. Most of the time it's generating files from inputs
|
||||
(user inputs and files).
|
||||
comes with an ArticlesGenerator and PagesGenerator, into others. Given a
|
||||
configuration, they can do whatever you want them to do. Most of the time
|
||||
it's generating files from inputs (user inputs and files).
|
||||
|
||||
I also deal with contents objects. They can be ``Articles``, ``Pages``,
|
||||
``Quotes``, or whatever you want. They are defined in the ``contents.py``
|
||||
|
|
@ -90,32 +85,30 @@ whole picture. I do use duck typing and not interfaces.
|
|||
|
||||
Internally, the following process is followed:
|
||||
|
||||
* First of all, the command line is parsed, and some content from
|
||||
the user is used to initialize the different generator objects.
|
||||
* First of all, the command line is parsed, and some content from the user is
|
||||
used to initialize the different generator objects.
|
||||
|
||||
* A ``context`` is created. It contains the settings from the command
|
||||
line and a settings file if provided.
|
||||
* A ``context`` is created. It contains the settings from the command line and
|
||||
a settings file if provided.
|
||||
* The ``generate_context`` method of each generator is called, updating
|
||||
the context.
|
||||
* The writer is created and given to the ``generate_output`` method of each
|
||||
generator.
|
||||
|
||||
* The writer is created and given to the ``generate_output`` method of
|
||||
each generator.
|
||||
|
||||
I make two calls because it is important that when the output is
|
||||
generated by the generators, the context will not change. In other
|
||||
words, the first method ``generate_context`` should modify the context,
|
||||
whereas the second ``generate_output`` method should not.
|
||||
I make two calls because it is important that when the output is generated by
|
||||
the generators, the context will not change. In other words, the first method
|
||||
``generate_context`` should modify the context, whereas the second
|
||||
``generate_output`` method should not.
|
||||
|
||||
Then, it is up to the generators to do what the want, in the
|
||||
``generate_context`` and ``generate_content`` method.
|
||||
Taking the ``ArticlesGenerator`` class will help to understand some others
|
||||
concepts. Here is what happens when calling the ``generate_context``
|
||||
method:
|
||||
``generate_context`` and ``generate_content`` method. Taking the
|
||||
``ArticlesGenerator`` class will help to understand some others concepts. Here
|
||||
is what happens when calling the ``generate_context`` method:
|
||||
|
||||
* Read the folder “path”, looking for restructured text files, load
|
||||
each of them, and construct a content object (``Article``) with it. To do so,
|
||||
use ``Reader`` objects.
|
||||
* Read the folder “path”, looking for restructured text files, load each of
|
||||
them, and construct a content object (``Article``) with it. To do so, use
|
||||
``Reader`` objects.
|
||||
* Update the ``context`` with all those articles.
|
||||
|
||||
Then, the ``generate_content`` method uses the ``context`` and the ``writer`` to
|
||||
generate the wanted output.
|
||||
Then, the ``generate_content`` method uses the ``context`` and the ``writer``
|
||||
to generate the wanted output.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
182
docs/themes.rst
182
docs/themes.rst
|
|
@ -3,13 +3,14 @@
|
|||
Creating themes
|
||||
###############
|
||||
|
||||
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
|
||||
To generate its HTML output, Pelican uses the `Jinja
|
||||
<https://palletsprojects.com/p/jinja/>`_ 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::
|
||||
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
|
||||
|
||||
|
|
@ -42,9 +43,9 @@ To make your own theme, you must follow the following structure::
|
|||
`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.
|
||||
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` contains all the templates that will be used to generate the
|
||||
content. 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-variables:
|
||||
|
|
@ -74,51 +75,53 @@ output_file The name of the file currently being generated. For
|
|||
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.). Sometimes this is shadowed (for instance in
|
||||
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.
|
||||
drafts The list of draft articles
|
||||
tags A list of (tag, articles) tuples, containing all
|
||||
the tags.
|
||||
authors A list of (author, articles) tuples, containing all
|
||||
the authors and corresponding articles (values)
|
||||
categories A list of (category, articles) tuples, containing
|
||||
all the categories and corresponding articles (values)
|
||||
tags A list of (tag, articles) tuples, containing all
|
||||
the tags and corresponding articles (values)
|
||||
pages The list of pages
|
||||
hidden_pages The list of hidden pages
|
||||
draft_pages The list of draft pages
|
||||
============= ===================================================
|
||||
|
||||
|
||||
Sorting
|
||||
-------
|
||||
|
||||
URL wrappers (currently categories, tags, and authors), have
|
||||
comparison methods that allow them to be easily sorted by name::
|
||||
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.
|
||||
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
|
||||
__ https://jinja.palletsprojects.com/en/master/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::
|
||||
(``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
|
||||
.. _datetime: https://docs.python.org/3/library/datetime.html#datetime-objects
|
||||
.. _strftime: https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior
|
||||
|
||||
|
||||
index.html
|
||||
|
|
@ -126,7 +129,8 @@ 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 ``index{number}.html``.
|
||||
If pagination is active, subsequent pages will reside in
|
||||
``index{number}.html``.
|
||||
|
||||
====================== ===================================================
|
||||
Variable Description
|
||||
|
|
@ -152,10 +156,10 @@ page_name 'index' -- useful for pagination links
|
|||
author.html
|
||||
-------------
|
||||
|
||||
This template will be processed for each of the existing authors, with
|
||||
output generated according to the ``AUTHOR_SAVE_AS`` setting (`Default:`
|
||||
``author/{author_name}.html``). If pagination is active, subsequent pages will by
|
||||
default reside at ``author/{author_name}{number}.html``.
|
||||
This template will be processed for each of the existing authors, with output
|
||||
generated according to the ``AUTHOR_SAVE_AS`` setting (`Default:`
|
||||
``author/{slug}.html``). If pagination is active, subsequent pages will by
|
||||
default reside at ``author/{slug}{number}.html``.
|
||||
|
||||
====================== ===================================================
|
||||
Variable Description
|
||||
|
|
@ -188,8 +192,8 @@ category.html
|
|||
|
||||
This template will be processed for each of the existing categories, with
|
||||
output generated according to the ``CATEGORY_SAVE_AS`` setting (`Default:`
|
||||
``category/{category_name}.html``). If pagination is active, subsequent pages will by
|
||||
default reside at ``category/{category_name}{number}.html``.
|
||||
``category/{slug}.html``). If pagination is active, subsequent pages will by
|
||||
default reside at ``category/{slug}{number}.html``.
|
||||
|
||||
====================== ===================================================
|
||||
Variable Description
|
||||
|
|
@ -220,10 +224,9 @@ page_name CATEGORY_URL where everything after `{slug}` is
|
|||
article.html
|
||||
-------------
|
||||
|
||||
This template will be processed for each article, with
|
||||
output generated according to the ``ARTICLE_SAVE_AS`` setting (`Default:`
|
||||
``{article_name}.html``). The following variables are available when
|
||||
rendering.
|
||||
This template will be processed for each article, with output generated
|
||||
according to the ``ARTICLE_SAVE_AS`` setting (`Default:` ``{slug}.html``). The
|
||||
following variables are available when rendering.
|
||||
|
||||
============= ===================================================
|
||||
Variable Description
|
||||
|
|
@ -232,9 +235,9 @@ 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.
|
||||
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:
|
||||
|
|
@ -250,8 +253,8 @@ metadata, as shown below:
|
|||
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:
|
||||
`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
|
||||
|
||||
|
|
@ -261,10 +264,9 @@ image for the Facebook open graph tags that will change for each article:
|
|||
page.html
|
||||
---------
|
||||
|
||||
This template will be processed for each page, with
|
||||
output generated according to the ``PAGE_SAVE_AS`` setting (`Default:`
|
||||
``pages/{page_name}.html``). The following variables are available when
|
||||
rendering.
|
||||
This template will be processed for each page, with output generated according
|
||||
to the ``PAGE_SAVE_AS`` setting (`Default:` ``pages/{slug}.html``). The
|
||||
following variables are available when rendering.
|
||||
|
||||
============= ===================================================
|
||||
Variable Description
|
||||
|
|
@ -277,10 +279,10 @@ page The page object to be displayed. You can access its
|
|||
tag.html
|
||||
--------
|
||||
|
||||
This template will be processed for each tag, with
|
||||
output generated according to the ``TAG_SAVE_AS`` setting (`Default:`
|
||||
``tag/{tag_name}.html``). If pagination is active, subsequent pages will by
|
||||
default reside at ``tag/{tag_name}{number}.html``.
|
||||
This template will be processed for each tag, with output generated according
|
||||
to the ``TAG_SAVE_AS`` setting (`Default:` ``tag/{slug}.html``). If pagination
|
||||
is active, subsequent pages will by default reside at
|
||||
``tag/{slug}{number}.html``.
|
||||
|
||||
====================== ===================================================
|
||||
Variable Description
|
||||
|
|
@ -311,9 +313,9 @@ page_name TAG_URL where everything after `{slug}` is removed
|
|||
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.
|
||||
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
|
||||
|
|
@ -347,9 +349,9 @@ Article
|
|||
|
||||
The string representation of an Article is the `source_path` attribute.
|
||||
|
||||
=================== ===================================================
|
||||
====================== ===================================================
|
||||
Attribute Description
|
||||
=================== ===================================================
|
||||
====================== ===================================================
|
||||
author The :ref:`Author <object-author_cat_tag>` of
|
||||
this article.
|
||||
authors A list of :ref:`Authors <object-author_cat_tag>`
|
||||
|
|
@ -368,6 +370,7 @@ metadata Article header metadata `dict`.
|
|||
save_as Location to save the article page.
|
||||
slug Page slug.
|
||||
source_path Full system path of the article source file.
|
||||
relative_source_path Relative path from PATH_ to the article source file.
|
||||
status The article status, can be any of 'published' or
|
||||
'draft'.
|
||||
summary Rendered summary content.
|
||||
|
|
@ -378,7 +381,10 @@ title Title of the article.
|
|||
translations List of translations
|
||||
:ref:`Article <object-article>` objects.
|
||||
url URL to the article page.
|
||||
=================== ===================================================
|
||||
====================== ===================================================
|
||||
|
||||
.. _PATH: settings.html#PATH
|
||||
|
||||
|
||||
.. _object-author_cat_tag:
|
||||
|
||||
|
|
@ -406,34 +412,38 @@ Page
|
|||
|
||||
The string representation of a Page is the `source_path` attribute.
|
||||
|
||||
=================== ===================================================
|
||||
Attribute Description
|
||||
=================== ===================================================
|
||||
author The :ref:`Author <object-author_cat_tag>` of
|
||||
this page.
|
||||
content The rendered content of the page.
|
||||
date Datetime object representing the page date.
|
||||
date_format Either default date format or locale date format.
|
||||
default_template Default template name.
|
||||
in_default_lang Boolean representing if the article is written
|
||||
in the default language.
|
||||
lang Language of the article.
|
||||
locale_date Date formatted by the `date_format`.
|
||||
metadata Page header metadata `dict`.
|
||||
save_as Location to save the page.
|
||||
slug Page slug.
|
||||
source_path Full system path of the page source file.
|
||||
status The page status, can be any of 'published' or
|
||||
'draft'.
|
||||
summary Rendered summary content.
|
||||
tags List of :ref:`Tag <object-author_cat_tag>`
|
||||
objects.
|
||||
template Template name to use for rendering.
|
||||
title Title of the page.
|
||||
translations List of translations
|
||||
:ref:`Article <object-article>` objects.
|
||||
url URL to the page.
|
||||
=================== ===================================================
|
||||
===================== ===================================================
|
||||
Attribute Description
|
||||
===================== ===================================================
|
||||
author The :ref:`Author <object-author_cat_tag>` of
|
||||
this page.
|
||||
content The rendered content of the page.
|
||||
date Datetime object representing the page date.
|
||||
date_format Either default date format or locale date format.
|
||||
default_template Default template name.
|
||||
in_default_lang Boolean representing if the article is written
|
||||
in the default language.
|
||||
lang Language of the article.
|
||||
locale_date Date formatted by the `date_format`.
|
||||
metadata Page header metadata `dict`.
|
||||
save_as Location to save the page.
|
||||
slug Page slug.
|
||||
source_path Full system path of the page source file.
|
||||
relative_source_path Relative path from PATH_ to the page source file.
|
||||
status The page status, can be any of 'published', 'hidden' or
|
||||
'draft'.
|
||||
summary Rendered summary content.
|
||||
tags List of :ref:`Tag <object-author_cat_tag>`
|
||||
objects.
|
||||
template Template name to use for rendering.
|
||||
title Title of the page.
|
||||
translations List of translations
|
||||
:ref:`Article <object-article>` objects.
|
||||
url URL to the page.
|
||||
===================== ===================================================
|
||||
|
||||
.. _PATH: settings.html#PATH
|
||||
|
||||
|
||||
Feeds
|
||||
=====
|
||||
|
|
@ -463,9 +473,9 @@ Since version 3.0, Pelican supports inheritance from the ``simple`` theme, so
|
|||
you can re-use the ``simple`` theme templates in your own themes.
|
||||
|
||||
If one of the mandatory files in the ``templates/`` directory of your theme is
|
||||
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.
|
||||
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`` theme in your own themes by
|
||||
using the ``{% extends %}`` directive as in the following example:
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ Custom 404 Pages
|
|||
|
||||
When a browser requests a resource that the web server cannot find, the web
|
||||
server usually displays a generic "File not found" (404) error page that can be
|
||||
stark and unsightly. One way to provide an error page that matches the theme
|
||||
of your site is to create a custom 404 page (*not* an article), such as this
|
||||
stark and unsightly. One way to provide an error page that matches the theme of
|
||||
your site is to create a custom 404 page (*not* an article), such as this
|
||||
Markdown-formatted example stored in ``content/pages/404.md``::
|
||||
|
||||
Title: Not Found
|
||||
|
|
@ -29,8 +29,8 @@ For Apache::
|
|||
|
||||
ErrorDocument 404 /404.html
|
||||
|
||||
For Amazon S3, first navigate to the ``Static Site Hosting`` menu in the
|
||||
bucket settings on your AWS cosole. From there::
|
||||
For Amazon S3, first navigate to the ``Static Site Hosting`` menu in the bucket
|
||||
settings on your AWS cosole. From there::
|
||||
|
||||
Error Document: 404.html
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ repository, and if you want to publish that Pelican site in the form of Project
|
|||
Pages to this repository, you can then use the following::
|
||||
|
||||
$ pelican content -o output -s pelicanconf.py
|
||||
$ ghp-import output
|
||||
$ ghp-import output -b gh-pages
|
||||
$ git push origin gh-pages
|
||||
|
||||
The ``ghp-import output`` command updates the local ``gh-pages`` branch with
|
||||
|
|
@ -67,15 +67,10 @@ already exist). The ``git push origin gh-pages`` command updates the remote
|
|||
``gh-pages`` branch, effectively publishing the Pelican site.
|
||||
|
||||
.. note::
|
||||
The ``github`` target of the Makefile (and the ``gh_pages`` task of the Fabfile)
|
||||
created by the ``pelican-quickstart`` command
|
||||
publishes the Pelican site as Project Pages, as described above.
|
||||
|
||||
.. note:: ghp-import on Windows
|
||||
|
||||
Until `ghp-import Pull Request #25 <https://github.com/davisp/ghp-import/pull/25>`_
|
||||
is accepted, you will need to install a custom build of ghp-import:
|
||||
``pip install https://github.com/chevah/ghp-import/archive/win-support.zip``
|
||||
The ``github`` target of the Makefile (and the ``gh_pages`` task of
|
||||
``tasks.py``) created by the ``pelican-quickstart`` command publishes the
|
||||
Pelican site as Project Pages, as described above.
|
||||
|
||||
User Pages
|
||||
----------
|
||||
|
|
@ -87,7 +82,7 @@ your ``<username>.github.io`` repository on GitHub.
|
|||
Again, you can take advantage of ``ghp-import``::
|
||||
|
||||
$ pelican content -o output -s pelicanconf.py
|
||||
$ ghp-import output
|
||||
$ ghp-import output -b gh-pages
|
||||
$ git push git@github.com:elemoine/elemoine.github.io.git gh-pages:master
|
||||
|
||||
The ``git push`` command pushes the local ``gh-pages`` branch (freshly updated
|
||||
|
|
@ -98,6 +93,22 @@ by the ``ghp-import`` command) to the ``elemoine.github.io`` repository's
|
|||
|
||||
To publish your Pelican site as User Pages, feel free to adjust the
|
||||
``github`` target of the Makefile.
|
||||
|
||||
Another option for publishing to User Pages is to generate the output files in
|
||||
the root directory of the project.
|
||||
|
||||
For example, your main project folder is ``<username>.github.io`` and you can
|
||||
create the Pelican project in a subdirectory called ``Pelican``. Then from
|
||||
inside the ``Pelican`` folder you can run::
|
||||
|
||||
$ pelican content -o .. -s pelicanconf.py
|
||||
|
||||
Now you can push the whole project ``<username>.github.io`` to the master
|
||||
branch of your GitHub repository::
|
||||
|
||||
$ git push origin master
|
||||
|
||||
(assuming origin is set to your remote repository).
|
||||
|
||||
Custom 404 Pages
|
||||
----------------
|
||||
|
|
@ -108,8 +119,8 @@ relevant `GitHub docs <https://help.github.com/articles/custom-404-pages/>`_.
|
|||
Update your site on each commit
|
||||
-------------------------------
|
||||
|
||||
To automatically update your Pelican site on each commit, you can create
|
||||
a post-commit hook. For example, you can add the following to
|
||||
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
|
||||
|
|
@ -130,8 +141,8 @@ output directory. For example::
|
|||
|
||||
Note: use forward slashes, ``/``, even on Windows.
|
||||
|
||||
You can also use the ``EXTRA_PATH_METADATA`` mechanism
|
||||
to place a ``favicon.ico`` or ``robots.txt`` at the root of any site.
|
||||
You can also use the ``EXTRA_PATH_METADATA`` mechanism to place a
|
||||
``favicon.ico`` or ``robots.txt`` at the root of any site.
|
||||
|
||||
How to add YouTube or Vimeo Videos
|
||||
==================================
|
||||
|
|
@ -147,3 +158,25 @@ embed videos in the markup. You can use `reST video directive
|
|||
<https://gist.github.com/dbrgn/2922648>`_ for reST or `mdx_video plugin
|
||||
<https://github.com/italomaia/mdx-video>`_ for Markdown.
|
||||
|
||||
|
||||
Develop Locally Using SSL
|
||||
==================================
|
||||
|
||||
Here's how you can set up your local pelican server to support SSL.
|
||||
|
||||
First, create a self-signed certificate and key using ``openssl`` (this creates ``cert.pem`` and ``key.pem``)::
|
||||
|
||||
$ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
|
||||
|
||||
And use this command to launch the server (the server starts within your ``output`` directory)::
|
||||
|
||||
python -m pelican.server 8443 --key=../key.pem --cert=../cert.pem
|
||||
|
||||
If you are using ``develop-server.sh``, add this to the top::
|
||||
|
||||
CERT="$BASEDIR/cert.pem"
|
||||
KEY="$BASEDIR/key.pem"
|
||||
|
||||
and modify the ``pelican.server`` line as follows::
|
||||
|
||||
$PY -m pelican.server $port --ssl --cert="$CERT" --key="$KEY" &
|
||||
|
|
@ -1,31 +1,41 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import locale
|
||||
import logging
|
||||
import multiprocessing
|
||||
import os
|
||||
import re
|
||||
import pprint
|
||||
import sys
|
||||
import time
|
||||
|
||||
import six
|
||||
import traceback
|
||||
from collections.abc import Iterable
|
||||
# Combines all paths to `pelican` package accessible from `sys.path`
|
||||
# Makes it possible to install `pelican` and namespace plugins into different
|
||||
# locations in the file system (e.g. pip with `-e` or `--user`)
|
||||
from pkgutil import extend_path
|
||||
__path__ = extend_path(__path__, __name__)
|
||||
|
||||
# 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 # noqa
|
||||
from pelican import signals
|
||||
from pelican.generators import (ArticlesGenerator, PagesGenerator,
|
||||
SourceFileGenerator, StaticGenerator,
|
||||
TemplatePagesGenerator)
|
||||
from pelican.log import init as init_logging
|
||||
from pelican.generators import (ArticlesGenerator, # noqa: I100
|
||||
PagesGenerator, SourceFileGenerator,
|
||||
StaticGenerator, TemplatePagesGenerator)
|
||||
from pelican.plugins import signals
|
||||
from pelican.plugins._utils import load_plugins
|
||||
from pelican.readers import Readers
|
||||
from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer
|
||||
from pelican.settings import read_settings
|
||||
from pelican.utils import (clean_output_dir, file_watcher,
|
||||
folder_watcher, maybe_pluralize)
|
||||
from pelican.writers import Writer
|
||||
|
||||
__version__ = "3.7.2.dev0"
|
||||
try:
|
||||
__version__ = __import__('pkg_resources') \
|
||||
.get_distribution('pelican').version
|
||||
except Exception:
|
||||
__version__ = "unknown"
|
||||
|
||||
DEFAULT_CONFIG_NAME = 'pelicanconf.py'
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -40,7 +50,6 @@ class Pelican(object):
|
|||
|
||||
# define the default settings
|
||||
self.settings = settings
|
||||
self._handle_deprecation()
|
||||
|
||||
self.path = settings['PATH']
|
||||
self.theme = settings['THEME']
|
||||
|
|
@ -59,95 +68,25 @@ class Pelican(object):
|
|||
sys.path.insert(0, '')
|
||||
|
||||
def init_plugins(self):
|
||||
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, 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 `%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.')
|
||||
|
||||
self.settings['ARTICLE_URL'] = '{slug}/'
|
||||
self.settings['ARTICLE_LANG_URL'] = '{slug}-{lang}/'
|
||||
self.settings['PAGE_URL'] = 'pages/{slug}/'
|
||||
self.settings['PAGE_LANG_URL'] = 'pages/{slug}-{lang}/'
|
||||
|
||||
for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL',
|
||||
'PAGE_LANG_URL'):
|
||||
logger.warning("%s = '%s'", setting, self.settings[setting])
|
||||
|
||||
if self.settings.get('AUTORELOAD_IGNORE_CACHE'):
|
||||
logger.warning('Found deprecated `AUTORELOAD_IGNORE_CACHE` in '
|
||||
'settings. Use --ignore-cache instead.')
|
||||
self.settings.pop('AUTORELOAD_IGNORE_CACHE')
|
||||
|
||||
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.')
|
||||
|
||||
structure = self.settings['ARTICLE_PERMALINK_STRUCTURE']
|
||||
|
||||
# Convert %(variable) into {variable}.
|
||||
structure = re.sub(r'%\((\w+)\)s', r'{\g<1>}', structure)
|
||||
|
||||
# Convert %x into {date:%x} for strftime
|
||||
structure = re.sub(r'(%[A-z])', r'{date:\g<1>}', structure)
|
||||
|
||||
# Strip a / prefix
|
||||
structure = re.sub('^/', '', structure)
|
||||
|
||||
for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL',
|
||||
'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])
|
||||
|
||||
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]
|
||||
self.plugins = load_plugins(self.settings)
|
||||
for plugin in self.plugins:
|
||||
logger.debug('Registering plugin `%s`', plugin.__name__)
|
||||
try:
|
||||
plugin.register()
|
||||
except Exception as e:
|
||||
logger.error('Cannot register plugin `%s`\n%s',
|
||||
plugin.__name__, e)
|
||||
|
||||
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
|
||||
# Share these among all the generators and content objects
|
||||
# They map source paths to Content objects or None
|
||||
context['generated_content'] = {}
|
||||
context['static_links'] = set()
|
||||
context['static_content'] = {}
|
||||
context['localsiteurl'] = self.settings['SITEURL']
|
||||
|
||||
generators = [
|
||||
|
|
@ -170,6 +109,10 @@ class Pelican(object):
|
|||
if hasattr(p, 'generate_context'):
|
||||
p.generate_context()
|
||||
|
||||
for p in generators:
|
||||
if hasattr(p, 'refresh_metadata_intersite_links'):
|
||||
p.refresh_metadata_intersite_links()
|
||||
|
||||
signals.all_generators_finalized.send(generators)
|
||||
|
||||
writer = self.get_writer()
|
||||
|
|
@ -205,13 +148,20 @@ class Pelican(object):
|
|||
len(pages_generator.hidden_translations)),
|
||||
'hidden page',
|
||||
'hidden pages')
|
||||
pluralized_draft_pages = maybe_pluralize(
|
||||
(len(pages_generator.draft_pages) +
|
||||
len(pages_generator.draft_translations)),
|
||||
'draft page',
|
||||
'draft pages')
|
||||
|
||||
print('Done: Processed {}, {}, {} and {} in {:.2f} seconds.'.format(
|
||||
pluralized_articles,
|
||||
pluralized_drafts,
|
||||
pluralized_pages,
|
||||
pluralized_hidden_pages,
|
||||
time.time() - start_time))
|
||||
print('Done: Processed {}, {}, {}, {} and {} in {:.2f} seconds.'
|
||||
.format(
|
||||
pluralized_articles,
|
||||
pluralized_drafts,
|
||||
pluralized_pages,
|
||||
pluralized_hidden_pages,
|
||||
pluralized_draft_pages,
|
||||
time.time() - start_time))
|
||||
|
||||
def get_generator_classes(self):
|
||||
generators = [ArticlesGenerator, PagesGenerator]
|
||||
|
|
@ -224,7 +174,7 @@ class Pelican(object):
|
|||
for pair in signals.get_generators.send(self):
|
||||
(funct, value) = pair
|
||||
|
||||
if not isinstance(value, collections.Iterable):
|
||||
if not isinstance(value, Iterable):
|
||||
value = (value, )
|
||||
|
||||
for v in value:
|
||||
|
|
@ -255,7 +205,33 @@ class Pelican(object):
|
|||
return writer(self.output_path, settings=self.settings)
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
class PrintSettings(argparse.Action):
|
||||
def __call__(self, parser, namespace, values, option_string):
|
||||
instance, settings = get_instance(namespace)
|
||||
|
||||
if values:
|
||||
# One or more arguments provided, so only print those settings
|
||||
for setting in values:
|
||||
if setting in settings:
|
||||
# Only add newline between setting name and value if dict
|
||||
if isinstance(settings[setting], dict):
|
||||
setting_format = '\n{}:\n{}'
|
||||
else:
|
||||
setting_format = '\n{}: {}'
|
||||
print(setting_format.format(
|
||||
setting,
|
||||
pprint.pformat(settings[setting])))
|
||||
else:
|
||||
print('\n{} is not a recognized setting.'.format(setting))
|
||||
break
|
||||
else:
|
||||
# No argument was given to --print-settings, so print all settings
|
||||
pprint.pprint(settings)
|
||||
|
||||
parser.exit()
|
||||
|
||||
|
||||
def parse_arguments(argv=None):
|
||||
parser = argparse.ArgumentParser(
|
||||
description='A tool to generate a static blog, '
|
||||
' with restructured text input files.',
|
||||
|
|
@ -305,6 +281,12 @@ def parse_arguments():
|
|||
help='Relaunch pelican each time a modification occurs'
|
||||
' on the content files.')
|
||||
|
||||
parser.add_argument('--print-settings', dest='print_settings', nargs='*',
|
||||
action=PrintSettings, metavar='SETTING_NAME',
|
||||
help='Print current configuration settings and exit. '
|
||||
'Append one or more setting name arguments to see the '
|
||||
'values for specific settings only.')
|
||||
|
||||
parser.add_argument('--relative-urls', dest='relative_paths',
|
||||
action='store_true',
|
||||
help='Use relative urls in output, '
|
||||
|
|
@ -327,7 +309,29 @@ def parse_arguments():
|
|||
help=('Exit the program with non-zero status if any '
|
||||
'errors/warnings encountered.'))
|
||||
|
||||
return parser.parse_args()
|
||||
parser.add_argument('--logs-dedup-min-level', default='WARNING',
|
||||
choices=('DEBUG', 'INFO', 'WARNING', 'ERROR'),
|
||||
help=('Only enable log de-duplication for levels equal'
|
||||
' to or above the specified value'))
|
||||
|
||||
parser.add_argument('-l', '--listen', dest='listen', action='store_true',
|
||||
help='Serve content files via HTTP and port 8000.')
|
||||
|
||||
parser.add_argument('-p', '--port', dest='port', type=int,
|
||||
help='Port to serve HTTP files at. (default: 8000)')
|
||||
|
||||
parser.add_argument('-b', '--bind', dest='bind',
|
||||
help='IP to bind to when serving files via HTTP '
|
||||
'(default: 127.0.0.1)')
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if args.port is not None and not args.listen:
|
||||
logger.warning('--port without --listen has no effect')
|
||||
if args.bind is not None and not args.listen:
|
||||
logger.warning('--bind without --listen has no effect')
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def get_config(args):
|
||||
|
|
@ -350,17 +354,12 @@ def get_config(args):
|
|||
config['WRITE_SELECTED'] = args.selected_paths.split(',')
|
||||
if args.relative_paths:
|
||||
config['RELATIVE_URLS'] = args.relative_paths
|
||||
if args.port is not None:
|
||||
config['PORT'] = args.port
|
||||
if args.bind is not None:
|
||||
config['BIND'] = args.bind
|
||||
config['DEBUG'] = args.verbosity == logging.DEBUG
|
||||
|
||||
# argparse returns bytes in Py2. There is no definite answer as to which
|
||||
# encoding argparse (or sys.argv) uses.
|
||||
# "Best" option seems to be locale.getpreferredencoding()
|
||||
# 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
|
||||
|
||||
|
||||
|
|
@ -368,13 +367,13 @@ def get_instance(args):
|
|||
|
||||
config_file = args.settings
|
||||
if config_file is None and os.path.isfile(DEFAULT_CONFIG_NAME):
|
||||
config_file = DEFAULT_CONFIG_NAME
|
||||
args.settings = DEFAULT_CONFIG_NAME
|
||||
config_file = DEFAULT_CONFIG_NAME
|
||||
args.settings = DEFAULT_CONFIG_NAME
|
||||
|
||||
settings = read_settings(config_file, override=get_config(args))
|
||||
|
||||
cls = settings['PELICAN_CLASS']
|
||||
if isinstance(cls, six.string_types):
|
||||
if isinstance(cls, str):
|
||||
module, cls_name = cls.rsplit('.', 1)
|
||||
module = __import__(module)
|
||||
cls = getattr(module, cls_name)
|
||||
|
|
@ -382,16 +381,123 @@ def get_instance(args):
|
|||
return cls(settings), settings
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_arguments()
|
||||
init(args.verbosity, args.fatal)
|
||||
def autoreload(watchers, args, old_static, reader_descs, excqueue=None):
|
||||
while True:
|
||||
try:
|
||||
# Check source dir for changed files ending with the given
|
||||
# extension in the settings. In the theme dir is no such
|
||||
# restriction; all files are recursively checked if they
|
||||
# have changed, no matter what extension the filenames
|
||||
# have.
|
||||
modified = {k: next(v) for k, v in watchers.items()}
|
||||
|
||||
if modified['settings']:
|
||||
pelican, settings = get_instance(args)
|
||||
|
||||
# Adjust static watchers if there are any changes
|
||||
new_static = settings.get("STATIC_PATHS", [])
|
||||
|
||||
# Added static paths
|
||||
# Add new watchers and set them as modified
|
||||
new_watchers = set(new_static).difference(old_static)
|
||||
for static_path in new_watchers:
|
||||
static_key = '[static]%s' % static_path
|
||||
watchers[static_key] = folder_watcher(
|
||||
os.path.join(pelican.path, static_path),
|
||||
[''],
|
||||
pelican.ignore_files)
|
||||
modified[static_key] = next(watchers[static_key])
|
||||
|
||||
# Removed static paths
|
||||
# Remove watchers and modified values
|
||||
old_watchers = set(old_static).difference(new_static)
|
||||
for static_path in old_watchers:
|
||||
static_key = '[static]%s' % static_path
|
||||
watchers.pop(static_key)
|
||||
modified.pop(static_key)
|
||||
|
||||
# Replace old_static with the new one
|
||||
old_static = new_static
|
||||
|
||||
if any(modified.values()):
|
||||
print('\n-> Modified: {}. re-generating...'.format(
|
||||
', '.join(k for k, v in modified.items() if v)))
|
||||
|
||||
if modified['content'] is None:
|
||||
logger.warning(
|
||||
'No valid files found in content for '
|
||||
+ 'the active readers:\n'
|
||||
+ '\n'.join(reader_descs))
|
||||
|
||||
if modified['theme'] is None:
|
||||
logger.warning('Empty theme folder. Using `basic` '
|
||||
'theme.')
|
||||
|
||||
pelican.run()
|
||||
|
||||
except KeyboardInterrupt as e:
|
||||
logger.warning("Keyboard interrupt, quitting.")
|
||||
if excqueue is not None:
|
||||
excqueue.put(traceback.format_exception_only(type(e), e)[-1])
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
if (args.verbosity == logging.DEBUG):
|
||||
if excqueue is not None:
|
||||
excqueue.put(
|
||||
traceback.format_exception_only(type(e), e)[-1])
|
||||
else:
|
||||
raise
|
||||
logger.warning(
|
||||
'Caught exception "%s". Reloading.', e)
|
||||
|
||||
finally:
|
||||
time.sleep(.5) # sleep to avoid cpu load
|
||||
|
||||
|
||||
def listen(server, port, output, excqueue=None):
|
||||
RootedHTTPServer.allow_reuse_address = True
|
||||
try:
|
||||
httpd = RootedHTTPServer(
|
||||
output, (server, port), ComplexHTTPRequestHandler)
|
||||
except OSError as e:
|
||||
logging.error("Could not listen on port %s, server %s.", port, server)
|
||||
if excqueue is not None:
|
||||
excqueue.put(traceback.format_exception_only(type(e), e)[-1])
|
||||
return
|
||||
|
||||
try:
|
||||
print("\nServing site at: {}:{} - Tap CTRL-C to stop".format(
|
||||
server, port))
|
||||
httpd.serve_forever()
|
||||
except Exception as e:
|
||||
if excqueue is not None:
|
||||
excqueue.put(traceback.format_exception_only(type(e), e)[-1])
|
||||
return
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\nKeyboard interrupt received. Shutting down server.")
|
||||
httpd.socket.close()
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
args = parse_arguments(argv)
|
||||
logs_dedup_min_level = getattr(logging, args.logs_dedup_min_level)
|
||||
init_logging(args.verbosity, args.fatal,
|
||||
logs_dedup_min_level=logs_dedup_min_level)
|
||||
|
||||
logger.debug('Pelican version: %s', __version__)
|
||||
logger.debug('Python version: %s', sys.version.split()[0])
|
||||
|
||||
try:
|
||||
pelican, settings = get_instance(args)
|
||||
|
||||
readers = Readers(settings)
|
||||
reader_descs = sorted(set(['%s (%s)' %
|
||||
(type(r).__name__,
|
||||
', '.join(r.file_extensions))
|
||||
for r in readers.readers.values()
|
||||
if r.enabled]))
|
||||
|
||||
watchers = {'content': folder_watcher(pelican.path,
|
||||
readers.extensions,
|
||||
|
|
@ -410,76 +516,34 @@ def main():
|
|||
[''],
|
||||
pelican.ignore_files)
|
||||
|
||||
if args.autoreload:
|
||||
if args.autoreload and args.listen:
|
||||
excqueue = multiprocessing.Queue()
|
||||
p1 = multiprocessing.Process(
|
||||
target=autoreload,
|
||||
args=(watchers, args, old_static, reader_descs, excqueue))
|
||||
p2 = multiprocessing.Process(
|
||||
target=listen,
|
||||
args=(settings.get('BIND'), settings.get('PORT'),
|
||||
settings.get("OUTPUT_PATH"), excqueue))
|
||||
p1.start()
|
||||
p2.start()
|
||||
exc = excqueue.get()
|
||||
p1.terminate()
|
||||
p2.terminate()
|
||||
logger.critical(exc)
|
||||
elif args.autoreload:
|
||||
print(' --- AutoReload Mode: Monitoring `content`, `theme` and'
|
||||
' `settings` for changes. ---')
|
||||
|
||||
while True:
|
||||
try:
|
||||
# Check source dir for changed files ending with the given
|
||||
# extension in the settings. In the theme dir is no such
|
||||
# restriction; all files are recursively checked if they
|
||||
# have changed, no matter what extension the filenames
|
||||
# have.
|
||||
modified = {k: next(v) for k, v in watchers.items()}
|
||||
|
||||
if modified['settings']:
|
||||
pelican, settings = get_instance(args)
|
||||
|
||||
# Adjust static watchers if there are any changes
|
||||
new_static = settings.get("STATIC_PATHS", [])
|
||||
|
||||
# Added static paths
|
||||
# Add new watchers and set them as modified
|
||||
new_watchers = set(new_static).difference(old_static)
|
||||
for static_path in new_watchers:
|
||||
static_key = '[static]%s' % static_path
|
||||
watchers[static_key] = folder_watcher(
|
||||
os.path.join(pelican.path, static_path),
|
||||
[''],
|
||||
pelican.ignore_files)
|
||||
modified[static_key] = next(watchers[static_key])
|
||||
|
||||
# Removed static paths
|
||||
# Remove watchers and modified values
|
||||
old_watchers = set(old_static).difference(new_static)
|
||||
for static_path in old_watchers:
|
||||
static_key = '[static]%s' % static_path
|
||||
watchers.pop(static_key)
|
||||
modified.pop(static_key)
|
||||
|
||||
# Replace old_static with the new one
|
||||
old_static = new_static
|
||||
|
||||
if any(modified.values()):
|
||||
print('\n-> Modified: {}. re-generating...'.format(
|
||||
', '.join(k for k, v in modified.items() if v)))
|
||||
|
||||
if modified['content'] is None:
|
||||
logger.warning('No valid files found in content.')
|
||||
|
||||
if modified['theme'] is None:
|
||||
logger.warning('Empty theme folder. Using `basic` '
|
||||
'theme.')
|
||||
|
||||
pelican.run()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.warning("Keyboard interrupt, quitting.")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
if (args.verbosity == logging.DEBUG):
|
||||
raise
|
||||
logger.warning(
|
||||
'Caught exception "%s". Reloading.', e)
|
||||
|
||||
finally:
|
||||
time.sleep(.5) # sleep to avoid cpu load
|
||||
|
||||
autoreload(watchers, args, old_static, reader_descs)
|
||||
elif args.listen:
|
||||
listen(settings.get('BIND'), settings.get('PORT'),
|
||||
settings.get("OUTPUT_PATH"))
|
||||
else:
|
||||
if next(watchers['content']) is None:
|
||||
logger.warning('No valid files found in content.')
|
||||
logger.warning(
|
||||
'No valid files found in content for '
|
||||
+ 'the active readers:\n'
|
||||
+ '\n'.join(reader_descs))
|
||||
|
||||
if next(watchers['theme']) is None:
|
||||
logger.warning('Empty theme folder. Using `basic` theme.')
|
||||
|
|
|
|||
9
pelican/__main__.py
Normal file
9
pelican/__main__.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
"""
|
||||
python -m pelican module entry point to run via python -m
|
||||
"""
|
||||
|
||||
from . import main
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -1,11 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
|
||||
from six.moves import cPickle as pickle
|
||||
import pickle
|
||||
|
||||
from pelican.utils import mkdir_p
|
||||
|
||||
|
|
@ -82,9 +80,7 @@ class FileStampDataCacher(FileDataCacher):
|
|||
and base path for filestamping operations
|
||||
"""
|
||||
|
||||
super(FileStampDataCacher, self).__init__(settings, cache_name,
|
||||
caching_policy,
|
||||
load_policy)
|
||||
super().__init__(settings, cache_name, caching_policy, load_policy)
|
||||
|
||||
method = self.settings['CHECK_MODIFIED_METHOD']
|
||||
if method == 'mtime':
|
||||
|
|
@ -106,7 +102,7 @@ class FileStampDataCacher(FileDataCacher):
|
|||
def cache_data(self, filename, data):
|
||||
"""Cache stamp and data for the given file"""
|
||||
stamp = self._get_file_stamp(filename)
|
||||
super(FileStampDataCacher, self).cache_data(filename, (stamp, data))
|
||||
super().cache_data(filename, (stamp, data))
|
||||
|
||||
def _get_file_stamp(self, filename):
|
||||
"""Check if the given file has been modified
|
||||
|
|
@ -134,8 +130,7 @@ class FileStampDataCacher(FileDataCacher):
|
|||
and current file stamp.
|
||||
"""
|
||||
|
||||
stamp, data = super(FileStampDataCacher, self).get_cached_data(
|
||||
filename, (None, default))
|
||||
stamp, data = super().get_cached_data(filename, (None, default))
|
||||
if stamp != self._get_file_stamp(filename):
|
||||
return default
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -1,25 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from urllib.parse import urljoin, urlparse, urlunparse
|
||||
|
||||
import pytz
|
||||
|
||||
import six
|
||||
from six.moves.urllib.parse import urlparse, urlunparse
|
||||
|
||||
from pelican import signals
|
||||
from pelican.plugins import signals
|
||||
from pelican.settings import DEFAULT_CONFIG
|
||||
from pelican.utils import (SafeDatetime, deprecated_attribute, memoized,
|
||||
path_to_url, posixize_path,
|
||||
python_2_unicode_compatible, sanitised_join,
|
||||
set_date_tzinfo, slugify, strftime,
|
||||
truncate_html_words)
|
||||
from pelican.utils import (deprecated_attribute, memoized, path_to_url,
|
||||
posixize_path, sanitised_join, set_date_tzinfo,
|
||||
slugify, truncate_html_words)
|
||||
|
||||
# Import these so that they're avalaible when you import from pelican.contents.
|
||||
from pelican.urlwrappers import (Author, Category, Tag, URLWrapper) # NOQA
|
||||
|
|
@ -27,7 +22,6 @@ from pelican.urlwrappers import (Author, Category, Tag, URLWrapper) # NOQA
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Content(object):
|
||||
"""Represents a content.
|
||||
|
||||
|
|
@ -98,16 +92,19 @@ class Content(object):
|
|||
if not hasattr(self, 'slug'):
|
||||
if (settings['SLUGIFY_SOURCE'] == 'title' and
|
||||
hasattr(self, 'title')):
|
||||
self.slug = slugify(self.title,
|
||||
settings.get('SLUG_SUBSTITUTIONS', ()))
|
||||
self.slug = slugify(
|
||||
self.title,
|
||||
regex_subs=settings.get('SLUG_REGEX_SUBSTITUTIONS', []))
|
||||
elif (settings['SLUGIFY_SOURCE'] == 'basename' and
|
||||
source_path is not None):
|
||||
basename = os.path.basename(
|
||||
os.path.splitext(source_path)[0])
|
||||
self.slug = slugify(
|
||||
basename, settings.get('SLUG_SUBSTITUTIONS', ()))
|
||||
basename,
|
||||
regex_subs=settings.get('SLUG_REGEX_SUBSTITUTIONS', []))
|
||||
|
||||
self.source_path = source_path
|
||||
self.relative_source_path = self.get_relative_source_path()
|
||||
|
||||
# manage the date format
|
||||
if not hasattr(self, 'date_format'):
|
||||
|
|
@ -118,34 +115,26 @@ class Content(object):
|
|||
|
||||
if isinstance(self.date_format, tuple):
|
||||
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]
|
||||
|
||||
# manage timezone
|
||||
default_timezone = settings.get('TIMEZONE', 'UTC')
|
||||
timezone = getattr(self, 'timezone', default_timezone)
|
||||
self.timezone = pytz.timezone(timezone)
|
||||
|
||||
if hasattr(self, 'date'):
|
||||
self.date = set_date_tzinfo(self.date, timezone)
|
||||
self.locale_date = strftime(self.date, self.date_format)
|
||||
self.locale_date = self.date.strftime(self.date_format)
|
||||
|
||||
if hasattr(self, 'modified'):
|
||||
self.modified = set_date_tzinfo(self.modified, timezone)
|
||||
self.locale_modified = strftime(self.modified, self.date_format)
|
||||
self.locale_modified = self.modified.strftime(self.date_format)
|
||||
|
||||
# manage status
|
||||
if not hasattr(self, 'status'):
|
||||
self.status = settings['DEFAULT_STATUS']
|
||||
if not settings['WITH_FUTURE_DATES'] and hasattr(self, 'date'):
|
||||
if self.date.tzinfo is None:
|
||||
now = SafeDatetime.now()
|
||||
else:
|
||||
now = SafeDatetime.utcnow().replace(tzinfo=pytz.utc)
|
||||
if self.date > now:
|
||||
self.status = 'draft'
|
||||
# Previous default of None broke comment plugins and perhaps others
|
||||
self.status = getattr(self, 'default_status', '')
|
||||
|
||||
# store the summary metadata if it is set
|
||||
if 'summary' in metadata:
|
||||
|
|
@ -156,13 +145,17 @@ class Content(object):
|
|||
def __str__(self):
|
||||
return self.source_path or repr(self)
|
||||
|
||||
def check_properties(self):
|
||||
def _has_valid_mandatory_properties(self):
|
||||
"""Test mandatory properties are set."""
|
||||
for prop in self.mandatory_properties:
|
||||
if not hasattr(self, prop):
|
||||
raise NameError(prop)
|
||||
logger.error(
|
||||
"Skipping %s: could not find information about '%s'",
|
||||
self, prop)
|
||||
return False
|
||||
return True
|
||||
|
||||
def valid_save_as(self):
|
||||
def _has_valid_save_as(self):
|
||||
"""Return true if save_as doesn't write outside output path, false
|
||||
otherwise."""
|
||||
try:
|
||||
|
|
@ -174,10 +167,35 @@ class Content(object):
|
|||
try:
|
||||
sanitised_join(output_path, self.save_as)
|
||||
except RuntimeError: # outside output_dir
|
||||
logger.error(
|
||||
"Skipping %s: file %r would be written outside output path",
|
||||
self,
|
||||
self.save_as,
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _has_valid_status(self):
|
||||
if hasattr(self, 'allowed_statuses'):
|
||||
if self.status not in self.allowed_statuses:
|
||||
logger.error(
|
||||
"Unknown status '%s' for file %s, skipping it.",
|
||||
self.status,
|
||||
self
|
||||
)
|
||||
return False
|
||||
|
||||
# if undefined we allow all
|
||||
return True
|
||||
|
||||
def is_valid(self):
|
||||
"""Validate Content"""
|
||||
# Use all() to not short circuit and get results of all validations
|
||||
return all([self._has_valid_mandatory_properties(),
|
||||
self._has_valid_save_as(),
|
||||
self._has_valid_status()])
|
||||
|
||||
@property
|
||||
def url_format(self):
|
||||
"""Returns the URL, formatted with the proper values"""
|
||||
|
|
@ -187,15 +205,16 @@ class Content(object):
|
|||
'path': path_to_url(path),
|
||||
'slug': getattr(self, 'slug', ''),
|
||||
'lang': getattr(self, 'lang', 'en'),
|
||||
'date': getattr(self, 'date', SafeDatetime.now()),
|
||||
'date': getattr(self, 'date', datetime.datetime.now()),
|
||||
'author': self.author.slug if hasattr(self, 'author') else '',
|
||||
'tag': self.tag.slug if hasattr(self, 'tag') else '',
|
||||
'category': self.category.slug if hasattr(self, 'category') else ''
|
||||
})
|
||||
return metadata
|
||||
|
||||
def _expand_settings(self, key):
|
||||
fq_key = ('%s_%s' % (self.__class__.__name__, key)).upper()
|
||||
def _expand_settings(self, key, klass=None):
|
||||
if not klass:
|
||||
klass = self.__class__.__name__
|
||||
fq_key = ('%s_%s' % (klass, key)).upper()
|
||||
return self.settings[fq_key].format(**self.url_format)
|
||||
|
||||
def get_url_setting(self, key):
|
||||
|
|
@ -204,6 +223,109 @@ class Content(object):
|
|||
key = key if self.in_default_lang else 'lang_%s' % key
|
||||
return self._expand_settings(key)
|
||||
|
||||
def _link_replacer(self, siteurl, m):
|
||||
what = m.group('what')
|
||||
value = urlparse(m.group('value'))
|
||||
path = value.path
|
||||
origin = m.group('path')
|
||||
|
||||
# urllib.parse.urljoin() produces `a.html` for urljoin("..", "a.html")
|
||||
# so if RELATIVE_URLS are enabled, we fall back to os.path.join() to
|
||||
# properly get `../a.html`. However, os.path.join() produces
|
||||
# `baz/http://foo/bar.html` for join("baz", "http://foo/bar.html")
|
||||
# instead of correct "http://foo/bar.html", so one has to pick a side
|
||||
# as there is no silver bullet.
|
||||
if self.settings['RELATIVE_URLS']:
|
||||
joiner = os.path.join
|
||||
else:
|
||||
joiner = urljoin
|
||||
|
||||
# However, it's not *that* simple: urljoin("blog", "index.html")
|
||||
# produces just `index.html` instead of `blog/index.html` (unlike
|
||||
# os.path.join()), so in order to get a correct answer one needs to
|
||||
# append a trailing slash to siteurl in that case. This also makes
|
||||
# the new behavior fully compatible with Pelican 3.7.1.
|
||||
if not siteurl.endswith('/'):
|
||||
siteurl += '/'
|
||||
|
||||
# XXX Put this in a different location.
|
||||
if what in {'filename', 'static', '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)
|
||||
)
|
||||
|
||||
key = 'static_content' if what in ('static', 'attach')\
|
||||
else 'generated_content'
|
||||
|
||||
def _get_linked_content(key, path):
|
||||
try:
|
||||
return self._context[key][path]
|
||||
except KeyError:
|
||||
try:
|
||||
# Markdown escapes spaces, try unescaping
|
||||
return self._context[key][path.replace('%20', ' ')]
|
||||
except KeyError:
|
||||
if what == 'filename' and key == 'generated_content':
|
||||
key = 'static_content'
|
||||
linked_content = _get_linked_content(key, path)
|
||||
if linked_content:
|
||||
logger.warning(
|
||||
'{filename} used for linking to static'
|
||||
' content %s in %s. Use {static} instead',
|
||||
path,
|
||||
self.get_relative_source_path())
|
||||
return linked_content
|
||||
return None
|
||||
|
||||
linked_content = _get_linked_content(key, path)
|
||||
if linked_content:
|
||||
if what == 'attach':
|
||||
linked_content.attach_to(self)
|
||||
origin = joiner(siteurl, linked_content.url)
|
||||
origin = origin.replace('\\', '/') # for Windows paths.
|
||||
else:
|
||||
logger.warning(
|
||||
"Unable to find '%s', skipping url replacement.",
|
||||
value.geturl(), extra={
|
||||
'limit_msg': ("Other resources were not found "
|
||||
"and their urls not replaced")})
|
||||
elif what == 'category':
|
||||
origin = joiner(siteurl, Category(path, self.settings).url)
|
||||
elif what == 'tag':
|
||||
origin = joiner(siteurl, Tag(path, self.settings).url)
|
||||
elif what == 'index':
|
||||
origin = joiner(siteurl, self.settings['INDEX_SAVE_AS'])
|
||||
elif what == 'author':
|
||||
origin = joiner(siteurl, Author(path, self.settings).url)
|
||||
else:
|
||||
logger.warning(
|
||||
"Replacement Indicator '%s' not recognized, "
|
||||
"skipping replacement",
|
||||
what)
|
||||
|
||||
# keep all other parts, such as query, fragment, etc.
|
||||
parts = list(value)
|
||||
parts[2] = origin
|
||||
origin = urlunparse(parts)
|
||||
|
||||
return ''.join((m.group('markup'), m.group('quote'), origin,
|
||||
m.group('quote')))
|
||||
|
||||
def _get_intrasite_link_regex(self):
|
||||
intrasite_link_regex = self.settings['INTRASITE_LINK_REGEX']
|
||||
regex = r"""
|
||||
(?P<markup><[^\>]+ # match tag with all url-value attributes
|
||||
(?:href|src|poster|data|cite|formaction|action)\s*=\s*)
|
||||
|
||||
(?P<quote>["\']) # require value to be quoted
|
||||
(?P<path>{0}(?P<value>.*?)) # the url value
|
||||
\2""".format(intrasite_link_regex)
|
||||
return re.compile(regex, re.X)
|
||||
|
||||
def _update_content(self, content, siteurl):
|
||||
"""Update the content attribute.
|
||||
|
||||
|
|
@ -217,79 +339,28 @@ class Content(object):
|
|||
if not content:
|
||||
return content
|
||||
|
||||
instrasite_link_regex = self.settings['INTRASITE_LINK_REGEX']
|
||||
regex = r"""
|
||||
(?P<markup><[^\>]+ # match tag with all url-value attributes
|
||||
(?:href|src|poster|data|cite|formaction|action)\s*=\s*)
|
||||
hrefs = self._get_intrasite_link_regex()
|
||||
return hrefs.sub(lambda m: self._link_replacer(siteurl, m), content)
|
||||
|
||||
(?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):
|
||||
def get_static_links(self):
|
||||
static_links = set()
|
||||
hrefs = self._get_intrasite_link_regex()
|
||||
for m in hrefs.finditer(self._content):
|
||||
what = m.group('what')
|
||||
value = urlparse(m.group('value'))
|
||||
path = value.path
|
||||
origin = m.group('path')
|
||||
|
||||
# XXX Put this in a different location.
|
||||
if what in {'filename', 'attach'}:
|
||||
if path.startswith('/'):
|
||||
path = path[1:]
|
||||
else:
|
||||
# relative to the source path of this content
|
||||
path = self.get_relative_source_path(
|
||||
os.path.join(self.relative_dir, path)
|
||||
)
|
||||
|
||||
if path not in self._context['filenames']:
|
||||
unquoted_path = path.replace('%20', ' ')
|
||||
|
||||
if unquoted_path in self._context['filenames']:
|
||||
path = unquoted_path
|
||||
|
||||
linked_content = self._context['filenames'].get(path)
|
||||
if linked_content:
|
||||
if what == 'attach':
|
||||
if isinstance(linked_content, Static):
|
||||
linked_content.attach_to(self)
|
||||
else:
|
||||
logger.warning(
|
||||
"%s used {attach} link syntax on a "
|
||||
"non-static file. Use {filename} instead.",
|
||||
self.get_relative_source_path())
|
||||
origin = '/'.join((siteurl, linked_content.url))
|
||||
origin = origin.replace('\\', '/') # for Windows paths.
|
||||
else:
|
||||
logger.warning(
|
||||
"Unable to find '%s', skipping url replacement.",
|
||||
value.geturl(), extra={
|
||||
'limit_msg': ("Other resources were not found "
|
||||
"and their urls not replaced")})
|
||||
elif what == 'category':
|
||||
origin = '/'.join((siteurl, Category(path, self.settings).url))
|
||||
elif what == 'tag':
|
||||
origin = '/'.join((siteurl, Tag(path, self.settings).url))
|
||||
elif what == 'index':
|
||||
origin = '/'.join((siteurl, self.settings['INDEX_SAVE_AS']))
|
||||
elif what == 'author':
|
||||
origin = '/'.join((siteurl, Author(path, self.settings).url))
|
||||
if what not in {'static', 'attach'}:
|
||||
continue
|
||||
if path.startswith('/'):
|
||||
path = path[1:]
|
||||
else:
|
||||
logger.warning(
|
||||
"Replacement Indicator '%s' not recognized, "
|
||||
"skipping replacement",
|
||||
what)
|
||||
|
||||
# keep all other parts, such as query, fragment, etc.
|
||||
parts = list(value)
|
||||
parts[2] = origin
|
||||
origin = urlunparse(parts)
|
||||
|
||||
return ''.join((m.group('markup'), m.group('quote'), origin,
|
||||
m.group('quote')))
|
||||
|
||||
return hrefs.sub(replacer, content)
|
||||
# relative to the source path of this content
|
||||
path = self.get_relative_source_path(
|
||||
os.path.join(self.relative_dir, path)
|
||||
)
|
||||
path = path.replace('%20', ' ')
|
||||
static_links.add(path)
|
||||
return static_links
|
||||
|
||||
def get_siteurl(self):
|
||||
return self._context.get('localsiteurl', '')
|
||||
|
|
@ -313,14 +384,15 @@ class Content(object):
|
|||
This is based on the summary metadata if set, otherwise truncate the
|
||||
content.
|
||||
"""
|
||||
if hasattr(self, '_summary'):
|
||||
return self._update_content(self._summary, siteurl)
|
||||
if 'summary' in self.metadata:
|
||||
return self.metadata['summary']
|
||||
|
||||
if self.settings['SUMMARY_MAX_LENGTH'] is None:
|
||||
return self.content
|
||||
|
||||
return truncate_html_words(self.content,
|
||||
self.settings['SUMMARY_MAX_LENGTH'])
|
||||
self.settings['SUMMARY_MAX_LENGTH'],
|
||||
self.settings['SUMMARY_END_MARKER'])
|
||||
|
||||
@property
|
||||
def summary(self):
|
||||
|
|
@ -338,6 +410,15 @@ class Content(object):
|
|||
"""Dummy function"""
|
||||
pass
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self._status
|
||||
|
||||
@status.setter
|
||||
def status(self, value):
|
||||
# TODO maybe typecheck
|
||||
self._status = value.lower()
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return self.get_url_setting('url')
|
||||
|
|
@ -380,30 +461,72 @@ class Content(object):
|
|||
os.path.abspath(self.source_path),
|
||||
os.path.abspath(self.settings['PATH']))))
|
||||
|
||||
def refresh_metadata_intersite_links(self):
|
||||
for key in self.settings['FORMATTED_FIELDS']:
|
||||
if key in self.metadata and key != 'summary':
|
||||
value = self._update_content(
|
||||
self.metadata[key],
|
||||
self.get_siteurl()
|
||||
)
|
||||
self.metadata[key] = value
|
||||
setattr(self, key.lower(), value)
|
||||
|
||||
# _summary is an internal variable that some plugins may be writing to,
|
||||
# so ensure changes to it are picked up
|
||||
if ('summary' in self.settings['FORMATTED_FIELDS'] and
|
||||
'summary' in self.metadata):
|
||||
self._summary = self._update_content(
|
||||
self._summary,
|
||||
self.get_siteurl()
|
||||
)
|
||||
self.metadata['summary'] = self._summary
|
||||
|
||||
|
||||
class Page(Content):
|
||||
mandatory_properties = ('title',)
|
||||
allowed_statuses = ('published', 'hidden', 'draft')
|
||||
default_status = 'published'
|
||||
default_template = 'page'
|
||||
|
||||
def _expand_settings(self, key):
|
||||
klass = 'draft_page' if self.status == 'draft' else None
|
||||
return super()._expand_settings(key, klass)
|
||||
|
||||
class Article(Page):
|
||||
|
||||
class Article(Content):
|
||||
mandatory_properties = ('title', 'date', 'category')
|
||||
allowed_statuses = ('published', 'draft')
|
||||
default_status = 'published'
|
||||
default_template = 'article'
|
||||
|
||||
|
||||
class Draft(Page):
|
||||
mandatory_properties = ('title', 'category')
|
||||
default_template = 'article'
|
||||
|
||||
|
||||
class Quote(Page):
|
||||
base_properties = ('author', 'date')
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Static(Page):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Static, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# handle WITH_FUTURE_DATES (designate article to draft based on date)
|
||||
if not self.settings['WITH_FUTURE_DATES'] and hasattr(self, 'date'):
|
||||
if self.date.tzinfo is None:
|
||||
now = datetime.datetime.now()
|
||||
else:
|
||||
now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
|
||||
if self.date > now:
|
||||
self.status = 'draft'
|
||||
|
||||
# if we are a draft and there is no date provided, set max datetime
|
||||
if not hasattr(self, 'date') and self.status == 'draft':
|
||||
self.date = datetime.datetime.max.replace(tzinfo=self.timezone)
|
||||
|
||||
def _expand_settings(self, key):
|
||||
klass = 'draft' if self.status == 'draft' else 'article'
|
||||
return super()._expand_settings(key, klass)
|
||||
|
||||
|
||||
class Static(Content):
|
||||
mandatory_properties = ('title',)
|
||||
default_status = 'published'
|
||||
default_template = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._output_location_referenced = False
|
||||
|
||||
@deprecated_attribute(old='filepath', new='source_path', since=(3, 2, 0))
|
||||
|
|
@ -422,13 +545,13 @@ class Static(Page):
|
|||
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
|
||||
return super().url
|
||||
|
||||
@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
|
||||
return super().save_as
|
||||
|
||||
def attach_to(self, content):
|
||||
"""Override our output directory with that of the given content object.
|
||||
|
|
@ -482,25 +605,3 @@ class Static(Page):
|
|||
|
||||
self.override_save_as = new_save_as
|
||||
self.override_url = new_url
|
||||
|
||||
|
||||
def is_valid_content(content, f):
|
||||
try:
|
||||
content.check_properties()
|
||||
except NameError as e:
|
||||
logger.error(
|
||||
"Skipping %s: could not find information about '%s'",
|
||||
f, six.text_type(e))
|
||||
return False
|
||||
|
||||
if not content.valid_save_as():
|
||||
logger.error(
|
||||
"Skipping %s: file %r would be written outside output path",
|
||||
f,
|
||||
content.save_as,
|
||||
)
|
||||
# Note: future code might want to use a result variable instead, to
|
||||
# allow showing multiple error messages at once.
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import calendar
|
||||
import errno
|
||||
import fnmatch
|
||||
import logging
|
||||
import os
|
||||
from codecs import open
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
from itertools import chain, groupby
|
||||
|
|
@ -14,15 +13,12 @@ from operator import attrgetter
|
|||
from jinja2 import (BaseLoader, ChoiceLoader, Environment, FileSystemLoader,
|
||||
PrefixLoader, TemplateNotFound)
|
||||
|
||||
import six
|
||||
|
||||
from pelican import signals
|
||||
from pelican.cache import FileStampDataCacher
|
||||
from pelican.contents import Article, Draft, Page, Static, is_valid_content
|
||||
from pelican.contents import Article, Page, Static
|
||||
from pelican.plugins import signals
|
||||
from pelican.readers import Readers
|
||||
from pelican.utils import (DateFormatter, copy, copy_file_metadata, mkdir_p,
|
||||
posixize_path, process_translations,
|
||||
python_2_unicode_compatible)
|
||||
from pelican.utils import (DateFormatter, copy, mkdir_p, order_content,
|
||||
posixize_path, process_translations)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -32,7 +28,6 @@ class PelicanTemplateNotFound(Exception):
|
|||
pass
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Generator(object):
|
||||
"""Baseclass generator"""
|
||||
|
||||
|
|
@ -51,20 +46,25 @@ class Generator(object):
|
|||
|
||||
# templates cache
|
||||
self._templates = {}
|
||||
self._templates_path = []
|
||||
self._templates_path.append(os.path.expanduser(
|
||||
os.path.join(self.theme, 'templates')))
|
||||
self._templates_path += self.settings['EXTRA_TEMPLATES_PATHS']
|
||||
self._templates_path = list(self.settings['THEME_TEMPLATES_OVERRIDES'])
|
||||
|
||||
theme_path = os.path.dirname(os.path.abspath(__file__))
|
||||
theme_templates_path = os.path.expanduser(
|
||||
os.path.join(self.theme, 'templates'))
|
||||
self._templates_path.append(theme_templates_path)
|
||||
theme_loader = FileSystemLoader(theme_templates_path)
|
||||
|
||||
simple_theme_path = os.path.dirname(os.path.abspath(__file__))
|
||||
simple_loader = FileSystemLoader(
|
||||
os.path.join(simple_theme_path, "themes", "simple", "templates"))
|
||||
|
||||
simple_loader = FileSystemLoader(os.path.join(theme_path,
|
||||
"themes", "simple", "templates"))
|
||||
self.env = Environment(
|
||||
loader=ChoiceLoader([
|
||||
FileSystemLoader(self._templates_path),
|
||||
simple_loader, # implicit inheritance
|
||||
PrefixLoader({'!simple': simple_loader}) # explicit one
|
||||
PrefixLoader({
|
||||
'!simple': simple_loader,
|
||||
'!theme': theme_loader
|
||||
}) # explicit ones
|
||||
]),
|
||||
**self.settings['JINJA_ENVIRONMENT']
|
||||
)
|
||||
|
|
@ -78,6 +78,14 @@ class Generator(object):
|
|||
custom_filters = self.settings['JINJA_FILTERS']
|
||||
self.env.filters.update(custom_filters)
|
||||
|
||||
# get custom Jinja globals from user settings
|
||||
custom_globals = self.settings['JINJA_GLOBALS']
|
||||
self.env.globals.update(custom_globals)
|
||||
|
||||
# get custom Jinja tests from user settings
|
||||
custom_tests = self.settings['JINJA_TESTS']
|
||||
self.env.tests.update(custom_tests)
|
||||
|
||||
signals.generator_init.send(self)
|
||||
|
||||
def get_template(self, name):
|
||||
|
|
@ -86,12 +94,19 @@ class Generator(object):
|
|||
templates ready to use with Jinja2.
|
||||
"""
|
||||
if name not in self._templates:
|
||||
try:
|
||||
self._templates[name] = self.env.get_template(name + '.html')
|
||||
except TemplateNotFound:
|
||||
for ext in self.settings['TEMPLATE_EXTENSIONS']:
|
||||
try:
|
||||
self._templates[name] = self.env.get_template(name + ext)
|
||||
break
|
||||
except TemplateNotFound:
|
||||
continue
|
||||
|
||||
if name not in self._templates:
|
||||
raise PelicanTemplateNotFound(
|
||||
'[templates] unable to load {}.html from {}'.format(
|
||||
name, self._templates_path))
|
||||
'[templates] unable to load {}[{}] from {}'.format(
|
||||
name, ', '.join(self.settings['TEMPLATE_EXTENSIONS']),
|
||||
self._templates_path))
|
||||
|
||||
return self._templates[name]
|
||||
|
||||
def _include_path(self, path, extensions=None):
|
||||
|
|
@ -125,7 +140,7 @@ class Generator(object):
|
|||
extensions are allowed)
|
||||
"""
|
||||
# backward compatibility for older generators
|
||||
if isinstance(paths, six.string_types):
|
||||
if isinstance(paths, str):
|
||||
paths = [paths]
|
||||
|
||||
# group the exclude dir names by parent path, for use with os.walk()
|
||||
|
|
@ -134,7 +149,7 @@ class Generator(object):
|
|||
parent_path, subdir = os.path.split(os.path.join(self.path, e))
|
||||
exclusions_by_dirpath.setdefault(parent_path, set()).add(subdir)
|
||||
|
||||
files = []
|
||||
files = set()
|
||||
ignores = self.settings['IGNORE_FILES']
|
||||
for path in paths:
|
||||
# careful: os.path.join() will add a slash when path == ''.
|
||||
|
|
@ -142,48 +157,55 @@ class Generator(object):
|
|||
|
||||
if os.path.isdir(root):
|
||||
for dirpath, dirs, temp_files in os.walk(
|
||||
root, followlinks=True):
|
||||
drop = []
|
||||
root, topdown=True, followlinks=True):
|
||||
excl = exclusions_by_dirpath.get(dirpath, ())
|
||||
for d in dirs:
|
||||
# We copy the `dirs` list as we will modify it in the loop:
|
||||
for d in list(dirs):
|
||||
if (d in excl or
|
||||
any(fnmatch.fnmatch(d, ignore)
|
||||
for ignore in ignores)):
|
||||
drop.append(d)
|
||||
for d in drop:
|
||||
dirs.remove(d)
|
||||
if d in dirs:
|
||||
dirs.remove(d)
|
||||
|
||||
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)
|
||||
files.add(fp)
|
||||
elif os.path.exists(root) and self._include_path(path, extensions):
|
||||
files.append(path) # can't walk non-directories
|
||||
files.add(path) # can't walk non-directories
|
||||
return files
|
||||
|
||||
def add_source_path(self, content):
|
||||
def add_source_path(self, content, static=False):
|
||||
"""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
|
||||
key = 'static_content' if static else 'generated_content'
|
||||
self.context[key][location] = content
|
||||
|
||||
def _add_failed_source_path(self, path):
|
||||
def _add_failed_source_path(self, path, static=False):
|
||||
"""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'][posixize_path(os.path.normpath(path))] = None
|
||||
key = 'static_content' if static else 'generated_content'
|
||||
self.context[key][posixize_path(os.path.normpath(path))] = None
|
||||
|
||||
def _is_potential_source_path(self, path):
|
||||
def _is_potential_source_path(self, path, static=False):
|
||||
"""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 (posixize_path(os.path.normpath(path))
|
||||
in self.context['filenames'])
|
||||
key = 'static_content' if static else 'generated_content'
|
||||
return (posixize_path(os.path.normpath(path)) in self.context[key])
|
||||
|
||||
def add_static_links(self, content):
|
||||
"""Add file links in content to context to be processed as Static
|
||||
content.
|
||||
"""
|
||||
self.context['static_links'] |= content.get_static_links()
|
||||
|
||||
def _update_context(self, items):
|
||||
"""Update the context with the given items from the currrent
|
||||
|
|
@ -227,7 +249,7 @@ class CachingGenerator(Generator, FileStampDataCacher):
|
|||
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)
|
||||
return super()._get_file_stamp(filename)
|
||||
|
||||
|
||||
class _FileLoader(BaseLoader):
|
||||
|
|
@ -255,7 +277,7 @@ class TemplatePagesGenerator(Generator):
|
|||
template = self.env.get_template(source)
|
||||
rurls = self.settings['RELATIVE_URLS']
|
||||
writer.write_file(dest, template, self.context, rurls,
|
||||
override_output=True)
|
||||
override_output=True, url='')
|
||||
finally:
|
||||
del self.env.loader.loaders[0]
|
||||
|
||||
|
|
@ -274,75 +296,137 @@ class ArticlesGenerator(CachingGenerator):
|
|||
self.authors = defaultdict(list)
|
||||
self.drafts = [] # only drafts in default language
|
||||
self.drafts_translations = []
|
||||
super(ArticlesGenerator, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
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'):
|
||||
writer.write_feed(self.articles, self.context,
|
||||
self.settings['FEED_ATOM'])
|
||||
writer.write_feed(
|
||||
self.articles,
|
||||
self.context,
|
||||
self.settings['FEED_ATOM'],
|
||||
self.settings.get('FEED_ATOM_URL', self.settings['FEED_ATOM'])
|
||||
)
|
||||
|
||||
if self.settings.get('FEED_RSS'):
|
||||
writer.write_feed(self.articles, self.context,
|
||||
self.settings['FEED_RSS'], feed_type='rss')
|
||||
writer.write_feed(
|
||||
self.articles,
|
||||
self.context,
|
||||
self.settings['FEED_RSS'],
|
||||
self.settings.get('FEED_RSS_URL', self.settings['FEED_RSS']),
|
||||
feed_type='rss'
|
||||
)
|
||||
|
||||
if (self.settings.get('FEED_ALL_ATOM') or
|
||||
self.settings.get('FEED_ALL_RSS')):
|
||||
all_articles = list(self.articles)
|
||||
for article in self.articles:
|
||||
all_articles.extend(article.translations)
|
||||
all_articles.sort(key=attrgetter('date'), reverse=True)
|
||||
order_content(all_articles,
|
||||
order_by=self.settings['ARTICLE_ORDER_BY'])
|
||||
|
||||
if self.settings.get('FEED_ALL_ATOM'):
|
||||
writer.write_feed(all_articles, self.context,
|
||||
self.settings['FEED_ALL_ATOM'])
|
||||
writer.write_feed(
|
||||
all_articles,
|
||||
self.context,
|
||||
self.settings['FEED_ALL_ATOM'],
|
||||
self.settings.get('FEED_ALL_ATOM_URL',
|
||||
self.settings['FEED_ALL_ATOM'])
|
||||
)
|
||||
|
||||
if self.settings.get('FEED_ALL_RSS'):
|
||||
writer.write_feed(all_articles, self.context,
|
||||
self.settings['FEED_ALL_RSS'],
|
||||
feed_type='rss')
|
||||
writer.write_feed(
|
||||
all_articles,
|
||||
self.context,
|
||||
self.settings['FEED_ALL_RSS'],
|
||||
self.settings.get('FEED_ALL_RSS_URL',
|
||||
self.settings['FEED_ALL_RSS']),
|
||||
feed_type='rss'
|
||||
)
|
||||
|
||||
for cat, arts in self.categories:
|
||||
arts.sort(key=attrgetter('date'), reverse=True)
|
||||
if self.settings.get('CATEGORY_FEED_ATOM'):
|
||||
writer.write_feed(arts, self.context,
|
||||
self.settings['CATEGORY_FEED_ATOM']
|
||||
% cat.slug, feed_title=cat.name)
|
||||
writer.write_feed(
|
||||
arts,
|
||||
self.context,
|
||||
self.settings['CATEGORY_FEED_ATOM'].format(slug=cat.slug),
|
||||
self.settings.get(
|
||||
'CATEGORY_FEED_ATOM_URL',
|
||||
self.settings['CATEGORY_FEED_ATOM']).format(
|
||||
slug=cat.slug
|
||||
),
|
||||
feed_title=cat.name
|
||||
)
|
||||
|
||||
if self.settings.get('CATEGORY_FEED_RSS'):
|
||||
writer.write_feed(arts, self.context,
|
||||
self.settings['CATEGORY_FEED_RSS']
|
||||
% cat.slug, feed_title=cat.name,
|
||||
feed_type='rss')
|
||||
writer.write_feed(
|
||||
arts,
|
||||
self.context,
|
||||
self.settings['CATEGORY_FEED_RSS'].format(slug=cat.slug),
|
||||
self.settings.get(
|
||||
'CATEGORY_FEED_RSS_URL',
|
||||
self.settings['CATEGORY_FEED_RSS']).format(
|
||||
slug=cat.slug
|
||||
),
|
||||
feed_title=cat.name,
|
||||
feed_type='rss'
|
||||
)
|
||||
|
||||
for auth, arts in self.authors:
|
||||
arts.sort(key=attrgetter('date'), reverse=True)
|
||||
if self.settings.get('AUTHOR_FEED_ATOM'):
|
||||
writer.write_feed(arts, self.context,
|
||||
self.settings['AUTHOR_FEED_ATOM']
|
||||
% auth.slug, feed_title=auth.name)
|
||||
writer.write_feed(
|
||||
arts,
|
||||
self.context,
|
||||
self.settings['AUTHOR_FEED_ATOM'].format(slug=auth.slug),
|
||||
self.settings.get(
|
||||
'AUTHOR_FEED_ATOM_URL',
|
||||
self.settings['AUTHOR_FEED_ATOM']
|
||||
).format(slug=auth.slug),
|
||||
feed_title=auth.name
|
||||
)
|
||||
|
||||
if self.settings.get('AUTHOR_FEED_RSS'):
|
||||
writer.write_feed(arts, self.context,
|
||||
self.settings['AUTHOR_FEED_RSS']
|
||||
% auth.slug, feed_title=auth.name,
|
||||
feed_type='rss')
|
||||
writer.write_feed(
|
||||
arts,
|
||||
self.context,
|
||||
self.settings['AUTHOR_FEED_RSS'].format(slug=auth.slug),
|
||||
self.settings.get(
|
||||
'AUTHOR_FEED_RSS_URL',
|
||||
self.settings['AUTHOR_FEED_RSS']
|
||||
).format(slug=auth.slug),
|
||||
feed_title=auth.name,
|
||||
feed_type='rss'
|
||||
)
|
||||
|
||||
if (self.settings.get('TAG_FEED_ATOM') or
|
||||
self.settings.get('TAG_FEED_RSS')):
|
||||
for tag, arts in self.tags.items():
|
||||
arts.sort(key=attrgetter('date'), reverse=True)
|
||||
if self.settings.get('TAG_FEED_ATOM'):
|
||||
writer.write_feed(arts, self.context,
|
||||
self.settings['TAG_FEED_ATOM']
|
||||
% tag.slug, feed_title=tag.name)
|
||||
writer.write_feed(
|
||||
arts,
|
||||
self.context,
|
||||
self.settings['TAG_FEED_ATOM'].format(slug=tag.slug),
|
||||
self.settings.get(
|
||||
'TAG_FEED_ATOM_URL',
|
||||
self.settings['TAG_FEED_ATOM']
|
||||
).format(slug=tag.slug),
|
||||
feed_title=tag.name
|
||||
)
|
||||
|
||||
if self.settings.get('TAG_FEED_RSS'):
|
||||
writer.write_feed(arts, self.context,
|
||||
self.settings['TAG_FEED_RSS'] % tag.slug,
|
||||
feed_title=tag.name, feed_type='rss')
|
||||
writer.write_feed(
|
||||
arts,
|
||||
self.context,
|
||||
self.settings['TAG_FEED_RSS'].format(slug=tag.slug),
|
||||
self.settings.get(
|
||||
'TAG_FEED_RSS_URL',
|
||||
self.settings['TAG_FEED_RSS']
|
||||
).format(slug=tag.slug),
|
||||
feed_title=tag.name,
|
||||
feed_type='rss'
|
||||
)
|
||||
|
||||
if (self.settings.get('TRANSLATION_FEED_ATOM') or
|
||||
self.settings.get('TRANSLATION_FEED_RSS')):
|
||||
|
|
@ -351,16 +435,31 @@ class ArticlesGenerator(CachingGenerator):
|
|||
translations_feeds[article.lang].append(article)
|
||||
|
||||
for lang, items in translations_feeds.items():
|
||||
items.sort(key=attrgetter('date'), reverse=True)
|
||||
items = order_content(
|
||||
items, order_by=self.settings['ARTICLE_ORDER_BY'])
|
||||
if self.settings.get('TRANSLATION_FEED_ATOM'):
|
||||
writer.write_feed(
|
||||
items, self.context,
|
||||
self.settings['TRANSLATION_FEED_ATOM'] % lang)
|
||||
items,
|
||||
self.context,
|
||||
self.settings['TRANSLATION_FEED_ATOM']
|
||||
.format(lang=lang),
|
||||
self.settings.get(
|
||||
'TRANSLATION_FEED_ATOM_URL',
|
||||
self.settings['TRANSLATION_FEED_ATOM']
|
||||
).format(lang=lang),
|
||||
)
|
||||
if self.settings.get('TRANSLATION_FEED_RSS'):
|
||||
writer.write_feed(
|
||||
items, self.context,
|
||||
self.settings['TRANSLATION_FEED_RSS'] % lang,
|
||||
feed_type='rss')
|
||||
items,
|
||||
self.context,
|
||||
self.settings['TRANSLATION_FEED_RSS']
|
||||
.format(lang=lang),
|
||||
self.settings.get(
|
||||
'TRANSLATION_FEED_RSS_URL',
|
||||
self.settings['TRANSLATION_FEED_RSS']
|
||||
).format(lang=lang),
|
||||
feed_type='rss'
|
||||
)
|
||||
|
||||
def generate_articles(self, write):
|
||||
"""Generate the articles."""
|
||||
|
|
@ -369,7 +468,7 @@ class ArticlesGenerator(CachingGenerator):
|
|||
write(article.save_as, self.get_template(article.template),
|
||||
self.context, article=article, category=article.category,
|
||||
override_output=hasattr(article, 'override_save_as'),
|
||||
blog=True)
|
||||
url=article.url, blog=True)
|
||||
|
||||
def generate_period_archives(self, write):
|
||||
"""Generate per-year, per-month, and per-day archives."""
|
||||
|
|
@ -384,32 +483,38 @@ class ArticlesGenerator(CachingGenerator):
|
|||
'day': self.settings['DAY_ARCHIVE_SAVE_AS'],
|
||||
}
|
||||
|
||||
period_url = {
|
||||
'year': self.settings['YEAR_ARCHIVE_URL'],
|
||||
'month': self.settings['MONTH_ARCHIVE_URL'],
|
||||
'day': self.settings['DAY_ARCHIVE_URL'],
|
||||
}
|
||||
|
||||
period_date_key = {
|
||||
'year': attrgetter('date.year'),
|
||||
'month': attrgetter('date.year', 'date.month'),
|
||||
'day': attrgetter('date.year', 'date.month', 'date.day')
|
||||
}
|
||||
|
||||
def _generate_period_archives(dates, key, save_as_fmt):
|
||||
def _generate_period_archives(dates, key, save_as_fmt, url_fmt):
|
||||
"""Generate period archives from `dates`, grouped by
|
||||
`key` and written to `save_as`.
|
||||
"""
|
||||
# `dates` is already sorted by date
|
||||
for _period, group in groupby(dates, key=key):
|
||||
archive = list(group)
|
||||
articles = [a for a in self.articles if a in archive]
|
||||
# arbitrarily grab the first date so that the usual
|
||||
# format string syntax can be used for specifying the
|
||||
# period archive dates
|
||||
date = archive[0].date
|
||||
save_as = save_as_fmt.format(date=date)
|
||||
url = url_fmt.format(date=date)
|
||||
context = self.context.copy()
|
||||
|
||||
if key == period_date_key['year']:
|
||||
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)
|
||||
|
|
@ -418,62 +523,60 @@ class ArticlesGenerator(CachingGenerator):
|
|||
month_name,
|
||||
_period[2])
|
||||
|
||||
write(save_as, template, context,
|
||||
dates=archive, blog=True)
|
||||
write(save_as, template, context, articles=articles,
|
||||
dates=archive, template_name='period_archives',
|
||||
blog=True, url=url, all_articles=self.articles)
|
||||
|
||||
for period in 'year', 'month', 'day':
|
||||
save_as = period_save_as[period]
|
||||
url = period_url[period]
|
||||
if save_as:
|
||||
key = period_date_key[period]
|
||||
_generate_period_archives(self.dates, key, save_as)
|
||||
_generate_period_archives(self.dates, key, save_as, url)
|
||||
|
||||
def generate_direct_templates(self, write):
|
||||
"""Generate direct templates pages"""
|
||||
PAGINATED_TEMPLATES = self.settings['PAGINATED_DIRECT_TEMPLATES']
|
||||
for template in self.settings['DIRECT_TEMPLATES']:
|
||||
paginated = {}
|
||||
if template in PAGINATED_TEMPLATES:
|
||||
paginated = {'articles': self.articles, 'dates': self.dates}
|
||||
save_as = self.settings.get("%s_SAVE_AS" % template.upper(),
|
||||
'%s.html' % template)
|
||||
url = self.settings.get("%s_URL" % template.upper(),
|
||||
'%s.html' % template)
|
||||
if not save_as:
|
||||
continue
|
||||
|
||||
write(save_as, self.get_template(template),
|
||||
self.context, blog=True, paginated=paginated,
|
||||
page_name=os.path.splitext(save_as)[0])
|
||||
write(save_as, self.get_template(template), self.context,
|
||||
articles=self.articles, dates=self.dates, blog=True,
|
||||
template_name=template,
|
||||
page_name=os.path.splitext(save_as)[0], url=url)
|
||||
|
||||
def generate_tags(self, write):
|
||||
"""Generate Tags pages."""
|
||||
tag_template = self.get_template('tag')
|
||||
for tag, articles in self.tags.items():
|
||||
articles.sort(key=attrgetter('date'), reverse=True)
|
||||
dates = [article for article in self.dates if article in articles]
|
||||
write(tag.save_as, tag_template, self.context, tag=tag,
|
||||
articles=articles, dates=dates,
|
||||
paginated={'articles': articles, 'dates': dates}, blog=True,
|
||||
page_name=tag.page_name, all_articles=self.articles)
|
||||
url=tag.url, articles=articles, dates=dates,
|
||||
template_name='tag', blog=True, page_name=tag.page_name,
|
||||
all_articles=self.articles)
|
||||
|
||||
def generate_categories(self, write):
|
||||
"""Generate category pages."""
|
||||
category_template = self.get_template('category')
|
||||
for cat, articles in self.categories:
|
||||
articles.sort(key=attrgetter('date'), reverse=True)
|
||||
dates = [article for article in self.dates if article in articles]
|
||||
write(cat.save_as, category_template, self.context,
|
||||
write(cat.save_as, category_template, self.context, url=cat.url,
|
||||
category=cat, articles=articles, dates=dates,
|
||||
paginated={'articles': articles, 'dates': dates}, blog=True,
|
||||
page_name=cat.page_name, all_articles=self.articles)
|
||||
template_name='category', blog=True, page_name=cat.page_name,
|
||||
all_articles=self.articles)
|
||||
|
||||
def generate_authors(self, write):
|
||||
"""Generate Author pages."""
|
||||
author_template = self.get_template('author')
|
||||
for aut, articles in self.authors:
|
||||
articles.sort(key=attrgetter('date'), reverse=True)
|
||||
dates = [article for article in self.dates if article in articles]
|
||||
write(aut.save_as, author_template, self.context,
|
||||
author=aut, articles=articles, dates=dates,
|
||||
paginated={'articles': articles, 'dates': dates}, blog=True,
|
||||
url=aut.url, author=aut, articles=articles, dates=dates,
|
||||
template_name='author', blog=True,
|
||||
page_name=aut.page_name, all_articles=self.articles)
|
||||
|
||||
def generate_drafts(self, write):
|
||||
|
|
@ -482,7 +585,7 @@ class ArticlesGenerator(CachingGenerator):
|
|||
write(draft.save_as, self.get_template(draft.template),
|
||||
self.context, article=draft, category=draft.category,
|
||||
override_output=hasattr(draft, 'override_save_as'),
|
||||
blog=True, all_articles=self.articles)
|
||||
blog=True, all_articles=self.articles, url=draft.url)
|
||||
|
||||
def generate_pages(self, writer):
|
||||
"""Generate the pages on the disk"""
|
||||
|
|
@ -509,12 +612,10 @@ class ArticlesGenerator(CachingGenerator):
|
|||
for f in self.get_files(
|
||||
self.settings['ARTICLE_PATHS'],
|
||||
exclude=self.settings['ARTICLE_EXCLUDES']):
|
||||
article_or_draft = self.get_cached_data(f, None)
|
||||
if article_or_draft is None:
|
||||
# TODO needs overhaul, maybe nomad for read_file
|
||||
# solution, unified behaviour
|
||||
article = self.get_cached_data(f, None)
|
||||
if article is None:
|
||||
try:
|
||||
article_or_draft = self.readers.read_file(
|
||||
article = self.readers.read_file(
|
||||
base_path=self.path, path=f, content_class=Article,
|
||||
context=self.context,
|
||||
preread_signal=signals.article_generator_preread,
|
||||
|
|
@ -528,40 +629,27 @@ class ArticlesGenerator(CachingGenerator):
|
|||
self._add_failed_source_path(f)
|
||||
continue
|
||||
|
||||
if not is_valid_content(article_or_draft, f):
|
||||
if not article.is_valid():
|
||||
self._add_failed_source_path(f)
|
||||
continue
|
||||
|
||||
if article_or_draft.status.lower() == "published":
|
||||
pass
|
||||
elif article_or_draft.status.lower() == "draft":
|
||||
article_or_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)
|
||||
else:
|
||||
logger.error(
|
||||
"Unknown status '%s' for file %s, skipping it.",
|
||||
article_or_draft.status, f)
|
||||
self._add_failed_source_path(f)
|
||||
continue
|
||||
self.cache_data(f, article)
|
||||
|
||||
self.cache_data(f, article_or_draft)
|
||||
if article.status == "published":
|
||||
all_articles.append(article)
|
||||
elif article.status == "draft":
|
||||
all_drafts.append(article)
|
||||
self.add_source_path(article)
|
||||
self.add_static_links(article)
|
||||
|
||||
if article_or_draft.status.lower() == "published":
|
||||
all_articles.append(article_or_draft)
|
||||
else:
|
||||
all_drafts.append(article_or_draft)
|
||||
self.add_source_path(article_or_draft)
|
||||
def _process(arts):
|
||||
origs, translations = process_translations(
|
||||
arts, translation_id=self.settings['ARTICLE_TRANSLATION_ID'])
|
||||
origs = order_content(origs, self.settings['ARTICLE_ORDER_BY'])
|
||||
return origs, translations
|
||||
|
||||
self.articles, self.translations = process_translations(
|
||||
all_articles,
|
||||
order_by=self.settings['ARTICLE_ORDER_BY'])
|
||||
self.drafts, self.drafts_translations = \
|
||||
process_translations(all_drafts)
|
||||
self.articles, self.translations = _process(all_articles)
|
||||
self.drafts, self.drafts_translations = _process(all_drafts)
|
||||
|
||||
signals.article_generator_pretaxonomy.send(self)
|
||||
|
||||
|
|
@ -600,20 +688,32 @@ class ArticlesGenerator(CachingGenerator):
|
|||
self.generate_pages(writer)
|
||||
signals.article_writer_finalized.send(self, writer=writer)
|
||||
|
||||
def refresh_metadata_intersite_links(self):
|
||||
for e in chain(self.articles,
|
||||
self.translations,
|
||||
self.drafts,
|
||||
self.drafts_translations):
|
||||
if hasattr(e, 'refresh_metadata_intersite_links'):
|
||||
e.refresh_metadata_intersite_links()
|
||||
|
||||
|
||||
class PagesGenerator(CachingGenerator):
|
||||
"""Generate pages"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.pages = []
|
||||
self.translations = []
|
||||
self.hidden_pages = []
|
||||
self.hidden_translations = []
|
||||
super(PagesGenerator, self).__init__(*args, **kwargs)
|
||||
self.draft_pages = []
|
||||
self.draft_translations = []
|
||||
super().__init__(*args, **kwargs)
|
||||
signals.page_generator_init.send(self)
|
||||
|
||||
def generate_context(self):
|
||||
all_pages = []
|
||||
hidden_pages = []
|
||||
draft_pages = []
|
||||
for f in self.get_files(
|
||||
self.settings['PAGE_PATHS'],
|
||||
exclude=self.settings['PAGE_EXCLUDES']):
|
||||
|
|
@ -634,32 +734,32 @@ class PagesGenerator(CachingGenerator):
|
|||
self._add_failed_source_path(f)
|
||||
continue
|
||||
|
||||
if not is_valid_content(page, f):
|
||||
self._add_failed_source_path(f)
|
||||
continue
|
||||
|
||||
if page.status.lower() not in ("published", "hidden"):
|
||||
logger.error(
|
||||
"Unknown status '%s' for file %s, skipping it.",
|
||||
page.status, f)
|
||||
if not page.is_valid():
|
||||
self._add_failed_source_path(f)
|
||||
continue
|
||||
|
||||
self.cache_data(f, page)
|
||||
|
||||
if page.status.lower() == "published":
|
||||
if page.status == "published":
|
||||
all_pages.append(page)
|
||||
elif page.status.lower() == "hidden":
|
||||
elif page.status == "hidden":
|
||||
hidden_pages.append(page)
|
||||
elif page.status == "draft":
|
||||
draft_pages.append(page)
|
||||
self.add_source_path(page)
|
||||
self.add_static_links(page)
|
||||
|
||||
self.pages, self.translations = process_translations(
|
||||
all_pages,
|
||||
order_by=self.settings['PAGE_ORDER_BY'])
|
||||
self.hidden_pages, self.hidden_translations = \
|
||||
process_translations(hidden_pages)
|
||||
def _process(pages):
|
||||
origs, translations = process_translations(
|
||||
pages, translation_id=self.settings['PAGE_TRANSLATION_ID'])
|
||||
origs = order_content(origs, self.settings['PAGE_ORDER_BY'])
|
||||
return origs, translations
|
||||
|
||||
self._update_context(('pages', 'hidden_pages'))
|
||||
self.pages, self.translations = _process(all_pages)
|
||||
self.hidden_pages, self.hidden_translations = _process(hidden_pages)
|
||||
self.draft_pages, self.draft_translations = _process(draft_pages)
|
||||
|
||||
self._update_context(('pages', 'hidden_pages', 'draft_pages'))
|
||||
|
||||
self.save_cache()
|
||||
self.readers.save_cache()
|
||||
|
|
@ -667,41 +767,44 @@ class PagesGenerator(CachingGenerator):
|
|||
|
||||
def generate_output(self, writer):
|
||||
for page in chain(self.translations, self.pages,
|
||||
self.hidden_translations, self.hidden_pages):
|
||||
self.hidden_translations, self.hidden_pages,
|
||||
self.draft_translations, self.draft_pages):
|
||||
signals.page_generator_write_page.send(self, content=page)
|
||||
writer.write_file(
|
||||
page.save_as, self.get_template(page.template),
|
||||
self.context, page=page,
|
||||
relative_urls=self.settings['RELATIVE_URLS'],
|
||||
override_output=hasattr(page, 'override_save_as'))
|
||||
override_output=hasattr(page, 'override_save_as'),
|
||||
url=page.url)
|
||||
signals.page_writer_finalized.send(self, writer=writer)
|
||||
|
||||
def refresh_metadata_intersite_links(self):
|
||||
for e in chain(self.pages,
|
||||
self.hidden_pages,
|
||||
self.hidden_translations,
|
||||
self.draft_pages,
|
||||
self.draft_translations):
|
||||
if hasattr(e, 'refresh_metadata_intersite_links'):
|
||||
e.refresh_metadata_intersite_links()
|
||||
|
||||
|
||||
class StaticGenerator(Generator):
|
||||
"""copy static paths (what you want to copy, like images, medias etc.
|
||||
to output"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(StaticGenerator, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fallback_to_symlinks = False
|
||||
signals.static_generator_init.send(self)
|
||||
|
||||
def _copy_paths(self, paths, source, destination, output_path,
|
||||
final_path=None):
|
||||
"""Copy all the paths from source to destination"""
|
||||
for path in paths:
|
||||
if final_path:
|
||||
copy(os.path.join(source, path),
|
||||
os.path.join(output_path, destination, final_path),
|
||||
self.settings['IGNORE_FILES'])
|
||||
else:
|
||||
copy(os.path.join(source, path),
|
||||
os.path.join(output_path, destination, path),
|
||||
self.settings['IGNORE_FILES'])
|
||||
|
||||
def generate_context(self):
|
||||
self.staticfiles = []
|
||||
for f in self.get_files(self.settings['STATIC_PATHS'],
|
||||
exclude=self.settings['STATIC_EXCLUDES'],
|
||||
extensions=False):
|
||||
linked_files = {os.path.join(self.path, path)
|
||||
for path in self.context['static_links']}
|
||||
found_files = self.get_files(self.settings['STATIC_PATHS'],
|
||||
exclude=self.settings['STATIC_EXCLUDES'],
|
||||
extensions=False)
|
||||
for f in linked_files | found_files:
|
||||
|
||||
# skip content source files unless the user explicitly wants them
|
||||
if self.settings['STATIC_EXCLUDE_SOURCES']:
|
||||
|
|
@ -716,7 +819,7 @@ class StaticGenerator(Generator):
|
|||
context_signal=signals.static_generator_context,
|
||||
context_sender=self)
|
||||
self.staticfiles.append(static)
|
||||
self.add_source_path(static)
|
||||
self.add_source_path(static, static=True)
|
||||
self._update_context(('staticfiles',))
|
||||
signals.static_generator_finalized.send(self)
|
||||
|
||||
|
|
@ -724,13 +827,95 @@ class StaticGenerator(Generator):
|
|||
self._copy_paths(self.settings['THEME_STATIC_PATHS'], self.theme,
|
||||
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))
|
||||
logger.info('Copying %s to %s', sc.source_path, sc.save_as)
|
||||
copy_file_metadata(source_path, save_as)
|
||||
if self._file_update_required(sc):
|
||||
self._link_or_copy_staticfile(sc)
|
||||
else:
|
||||
logger.debug('%s is up to date, not copying', sc.source_path)
|
||||
|
||||
def _copy_paths(self, paths, source, destination, output_path,
|
||||
final_path=None):
|
||||
"""Copy all the paths from source to destination"""
|
||||
for path in paths:
|
||||
source_path = os.path.join(source, path)
|
||||
|
||||
if final_path:
|
||||
if os.path.isfile(source_path):
|
||||
destination_path = os.path.join(output_path, destination,
|
||||
final_path,
|
||||
os.path.basename(path))
|
||||
else:
|
||||
destination_path = os.path.join(output_path, destination,
|
||||
final_path)
|
||||
else:
|
||||
destination_path = os.path.join(output_path, destination, path)
|
||||
|
||||
copy(source_path, destination_path,
|
||||
self.settings['IGNORE_FILES'])
|
||||
|
||||
def _file_update_required(self, staticfile):
|
||||
source_path = os.path.join(self.path, staticfile.source_path)
|
||||
save_as = os.path.join(self.output_path, staticfile.save_as)
|
||||
if not os.path.exists(save_as):
|
||||
return True
|
||||
elif (self.settings['STATIC_CREATE_LINKS'] and
|
||||
os.path.samefile(source_path, save_as)):
|
||||
return False
|
||||
elif (self.settings['STATIC_CREATE_LINKS'] and
|
||||
os.path.realpath(save_as) == source_path):
|
||||
return False
|
||||
elif not self.settings['STATIC_CHECK_IF_MODIFIED']:
|
||||
return True
|
||||
else:
|
||||
return self._source_is_newer(staticfile)
|
||||
|
||||
def _source_is_newer(self, staticfile):
|
||||
source_path = os.path.join(self.path, staticfile.source_path)
|
||||
save_as = os.path.join(self.output_path, staticfile.save_as)
|
||||
s_mtime = os.path.getmtime(source_path)
|
||||
d_mtime = os.path.getmtime(save_as)
|
||||
return s_mtime - d_mtime > 0.000001
|
||||
|
||||
def _link_or_copy_staticfile(self, sc):
|
||||
if self.settings['STATIC_CREATE_LINKS']:
|
||||
self._link_staticfile(sc)
|
||||
else:
|
||||
self._copy_staticfile(sc)
|
||||
|
||||
def _copy_staticfile(self, sc):
|
||||
source_path = os.path.join(self.path, sc.source_path)
|
||||
save_as = os.path.join(self.output_path, sc.save_as)
|
||||
self._mkdir(os.path.dirname(save_as))
|
||||
copy(source_path, save_as)
|
||||
logger.info('Copying %s to %s', sc.source_path, sc.save_as)
|
||||
|
||||
def _link_staticfile(self, sc):
|
||||
source_path = os.path.join(self.path, sc.source_path)
|
||||
save_as = os.path.join(self.output_path, sc.save_as)
|
||||
self._mkdir(os.path.dirname(save_as))
|
||||
try:
|
||||
if os.path.lexists(save_as):
|
||||
os.unlink(save_as)
|
||||
logger.info('Linking %s and %s', sc.source_path, sc.save_as)
|
||||
if self.fallback_to_symlinks:
|
||||
os.symlink(source_path, save_as)
|
||||
else:
|
||||
os.link(source_path, save_as)
|
||||
except OSError as err:
|
||||
if err.errno == errno.EXDEV: # 18: Invalid cross-device link
|
||||
logger.debug(
|
||||
"Cross-device links not valid. "
|
||||
"Creating symbolic links instead."
|
||||
)
|
||||
self.fallback_to_symlinks = True
|
||||
self._link_staticfile(sc)
|
||||
else:
|
||||
raise err
|
||||
|
||||
def _mkdir(self, path):
|
||||
if os.path.lexists(path) and not os.path.isdir(path):
|
||||
os.unlink(path)
|
||||
mkdir_p(path)
|
||||
|
||||
|
||||
class SourceFileGenerator(Generator):
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from collections import Mapping, defaultdict
|
||||
|
||||
import six
|
||||
from collections import defaultdict
|
||||
|
||||
__all__ = [
|
||||
'init'
|
||||
|
|
@ -17,25 +13,25 @@ __all__ = [
|
|||
class BaseFormatter(logging.Formatter):
|
||||
def __init__(self, fmt=None, datefmt=None):
|
||||
FORMAT = '%(customlevelname)s %(message)s'
|
||||
super(BaseFormatter, self).__init__(fmt=FORMAT, datefmt=datefmt)
|
||||
super().__init__(fmt=FORMAT, datefmt=datefmt)
|
||||
|
||||
def format(self, record):
|
||||
customlevel = self._get_levelname(record.levelname)
|
||||
record.__dict__['customlevelname'] = customlevel
|
||||
# format multiline messages 'nicely' to make it clear they are together
|
||||
record.msg = record.msg.replace('\n', '\n | ')
|
||||
return super(BaseFormatter, self).format(record)
|
||||
record.args = tuple(arg.replace('\n', '\n | ') if
|
||||
isinstance(arg, str) else
|
||||
arg for arg in record.args)
|
||||
return super().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)
|
||||
s = super().formatException(ei)
|
||||
# fancy format traceback
|
||||
s = str('\n').join(str(' | ') + line for line in s.splitlines())
|
||||
s = '\n'.join(' | ' + line for line in s.splitlines())
|
||||
# separate the traceback from the preceding lines
|
||||
s = str(' |___\n{}').format(s)
|
||||
s = ' |___\n{}'.format(s)
|
||||
return s
|
||||
|
||||
def _get_levelname(self, name):
|
||||
|
|
@ -91,6 +87,8 @@ class LimitFilter(logging.Filter):
|
|||
E.g.: log.warning(('43 is not the answer', 'More erroneous answers'))
|
||||
"""
|
||||
|
||||
LOGS_DEDUP_MIN_LEVEL = logging.WARNING
|
||||
|
||||
_ignore = set()
|
||||
_raised_messages = set()
|
||||
_threshold = 5
|
||||
|
|
@ -98,7 +96,7 @@ class LimitFilter(logging.Filter):
|
|||
|
||||
def filter(self, record):
|
||||
# don't limit log messages for anything above "warning"
|
||||
if record.levelno > logging.WARN:
|
||||
if record.levelno > self.LOGS_DEDUP_MIN_LEVEL:
|
||||
return True
|
||||
|
||||
# extract group
|
||||
|
|
@ -112,11 +110,13 @@ class LimitFilter(logging.Filter):
|
|||
else:
|
||||
self._raised_messages.add(message_key)
|
||||
|
||||
# ignore LOG_FILTER records by templates when "debug" isn't enabled
|
||||
# ignore LOG_FILTER records by templates or messages
|
||||
# when "debug" isn't enabled
|
||||
logger_level = logging.getLogger().getEffectiveLevel()
|
||||
if logger_level > logging.DEBUG:
|
||||
ignore_key = (record.levelno, record.msg)
|
||||
if ignore_key in self._ignore:
|
||||
template_key = (record.levelno, record.msg)
|
||||
message_key = (record.levelno, record.getMessage())
|
||||
if (template_key in self._ignore or message_key in self._ignore):
|
||||
return False
|
||||
|
||||
# check if we went over threshold
|
||||
|
|
@ -131,41 +131,7 @@ class LimitFilter(logging.Filter):
|
|||
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('%s: %s') % (arg.__class__.__name__, arg)
|
||||
if six.PY2:
|
||||
text = text.decode(self._exc_encoding)
|
||||
return text
|
||||
else:
|
||||
return arg
|
||||
|
||||
|
||||
class LimitLogger(SafeLogger):
|
||||
class LimitLogger(logging.Logger):
|
||||
"""
|
||||
A logger which adds LimitFilter automatically
|
||||
"""
|
||||
|
|
@ -173,7 +139,7 @@ class LimitLogger(SafeLogger):
|
|||
limit_filter = LimitFilter()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(LimitLogger, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.enable_filter()
|
||||
|
||||
def disable_filter(self):
|
||||
|
|
@ -188,12 +154,12 @@ class FatalLogger(LimitLogger):
|
|||
errors_fatal = False
|
||||
|
||||
def warning(self, *args, **kwargs):
|
||||
super(FatalLogger, self).warning(*args, **kwargs)
|
||||
super().warning(*args, **kwargs)
|
||||
if FatalLogger.warnings_fatal:
|
||||
raise RuntimeError('Warning encountered')
|
||||
|
||||
def error(self, *args, **kwargs):
|
||||
super(FatalLogger, self).error(*args, **kwargs)
|
||||
super().error(*args, **kwargs)
|
||||
if FatalLogger.errors_fatal:
|
||||
raise RuntimeError('Error encountered')
|
||||
|
||||
|
|
@ -226,7 +192,8 @@ def get_formatter():
|
|||
return TextFormatter()
|
||||
|
||||
|
||||
def init(level=None, fatal='', handler=logging.StreamHandler(), name=None):
|
||||
def init(level=None, fatal='', handler=logging.StreamHandler(), name=None,
|
||||
logs_dedup_min_level=None):
|
||||
FatalLogger.warnings_fatal = fatal.startswith('warning')
|
||||
FatalLogger.errors_fatal = bool(fatal)
|
||||
|
||||
|
|
@ -237,6 +204,8 @@ def init(level=None, fatal='', handler=logging.StreamHandler(), name=None):
|
|||
|
||||
if level:
|
||||
logger.setLevel(level)
|
||||
if logs_dedup_min_level:
|
||||
LimitFilter.LOGS_DEDUP_MIN_LEVEL = logs_dedup_min_level
|
||||
|
||||
|
||||
def log_warnings():
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import functools
|
||||
import logging
|
||||
|
|
@ -7,8 +6,6 @@ import os
|
|||
from collections import namedtuple
|
||||
from math import ceil
|
||||
|
||||
import six
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
PaginationRule = namedtuple(
|
||||
'PaginationRule',
|
||||
|
|
@ -17,14 +14,14 @@ PaginationRule = namedtuple(
|
|||
|
||||
|
||||
class Paginator(object):
|
||||
def __init__(self, name, object_list, settings):
|
||||
def __init__(self, name, url, object_list, settings, per_page=None):
|
||||
self.name = name
|
||||
self.url = url
|
||||
self.object_list = object_list
|
||||
self.settings = settings
|
||||
|
||||
if settings.get('DEFAULT_PAGINATION'):
|
||||
self.per_page = settings.get('DEFAULT_PAGINATION')
|
||||
self.orphans = settings.get('DEFAULT_ORPHANS')
|
||||
if per_page:
|
||||
self.per_page = per_page
|
||||
self.orphans = settings['DEFAULT_ORPHANS']
|
||||
else:
|
||||
self.per_page = len(object_list)
|
||||
self.orphans = 0
|
||||
|
|
@ -37,8 +34,8 @@ class Paginator(object):
|
|||
top = bottom + self.per_page
|
||||
if top + self.orphans >= self.count:
|
||||
top = self.count
|
||||
return Page(self.name, self.object_list[bottom:top], number, self,
|
||||
self.settings)
|
||||
return Page(self.name, self.url, self.object_list[bottom:top], number,
|
||||
self, self.settings)
|
||||
|
||||
def _get_count(self):
|
||||
"Returns the total number of objects, across all pages."
|
||||
|
|
@ -65,8 +62,12 @@ class Paginator(object):
|
|||
|
||||
|
||||
class Page(object):
|
||||
def __init__(self, name, object_list, number, paginator, settings):
|
||||
def __init__(self, name, url, object_list, number, paginator, settings):
|
||||
self.full_name = name
|
||||
self.name, self.extension = os.path.splitext(name)
|
||||
dn, fn = os.path.split(name)
|
||||
self.base_name = dn if fn in ('index.htm', 'index.html') else self.name
|
||||
self.base_url = url
|
||||
self.object_list = object_list
|
||||
self.number = number
|
||||
self.paginator = paginator
|
||||
|
|
@ -127,28 +128,30 @@ class Page(object):
|
|||
|
||||
prop_value = getattr(rule, key)
|
||||
|
||||
if not isinstance(prop_value, six.string_types):
|
||||
if not isinstance(prop_value, str):
|
||||
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.replace(os.sep, '/'),
|
||||
'object_list': self.object_list,
|
||||
'number': self.number,
|
||||
'paginator': self.paginator,
|
||||
'settings': self.settings,
|
||||
'base_name': os.path.dirname(self.name),
|
||||
'number_sep': '/',
|
||||
'save_as': self.full_name,
|
||||
'url': self.base_url,
|
||||
'name': self.name,
|
||||
'base_name': self.base_name,
|
||||
'extension': self.extension,
|
||||
'number': self.number,
|
||||
}
|
||||
|
||||
if self.number == 1:
|
||||
# no page numbers on the first page
|
||||
context['number'] = ''
|
||||
context['number_sep'] = ''
|
||||
|
||||
ret = prop_value.format(**context)
|
||||
# Remove a single leading slash, if any. This is done for backwards
|
||||
# compatibility reasons. If a leading slash is needed (for URLs
|
||||
# relative to server root or absolute URLs without the scheme such as
|
||||
# //blog.my.site/), it can be worked around by prefixing the pagination
|
||||
# pattern by an additional slash (which then gets removed, preserving
|
||||
# the other slashes). This also means the following code *can't* be
|
||||
# changed to lstrip() because that would remove all leading slashes and
|
||||
# thus make the workaround impossible. See
|
||||
# test_custom_pagination_pattern() for a verification of this.
|
||||
if ret[0] == '/':
|
||||
ret = ret[1:]
|
||||
return ret
|
||||
|
|
|
|||
85
pelican/plugins/_utils.py
Normal file
85
pelican/plugins/_utils.py
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import importlib
|
||||
import importlib.machinery
|
||||
import importlib.util
|
||||
import logging
|
||||
import pkgutil
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def iter_namespace(ns_pkg):
|
||||
# Specifying the second argument (prefix) to iter_modules makes the
|
||||
# returned name an absolute name instead of a relative one. This allows
|
||||
# import_module to work without having to do additional modification to
|
||||
# the name.
|
||||
return pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + ".")
|
||||
|
||||
|
||||
def get_namespace_plugins(ns_pkg=None):
|
||||
if ns_pkg is None:
|
||||
import pelican.plugins as ns_pkg
|
||||
|
||||
return {
|
||||
name: importlib.import_module(name)
|
||||
for finder, name, ispkg
|
||||
in iter_namespace(ns_pkg)
|
||||
if ispkg
|
||||
}
|
||||
|
||||
|
||||
def list_plugins(ns_pkg=None):
|
||||
from pelican.log import init as init_logging
|
||||
init_logging(logging.INFO)
|
||||
ns_plugins = get_namespace_plugins(ns_pkg)
|
||||
if ns_plugins:
|
||||
logger.info('Plugins found:\n' + '\n'.join(ns_plugins))
|
||||
else:
|
||||
logger.info('No plugins are installed')
|
||||
|
||||
|
||||
def load_legacy_plugin(plugin, plugin_paths):
|
||||
# Try to find plugin in PLUGIN_PATHS
|
||||
spec = importlib.machinery.PathFinder.find_spec(plugin, plugin_paths)
|
||||
if spec is None:
|
||||
# If failed, try to find it in normal importable locations
|
||||
spec = importlib.util.find_spec(plugin)
|
||||
if spec is None:
|
||||
raise ImportError('Cannot import plugin `{}`'.format(plugin))
|
||||
else:
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
|
||||
def load_plugins(settings):
|
||||
logger.debug('Finding namespace plugins')
|
||||
namespace_plugins = get_namespace_plugins()
|
||||
if namespace_plugins:
|
||||
logger.debug('Namespace plugins found:\n' +
|
||||
'\n'.join(namespace_plugins))
|
||||
plugins = []
|
||||
if settings.get('PLUGINS') is not None:
|
||||
for plugin in settings['PLUGINS']:
|
||||
if isinstance(plugin, str):
|
||||
logger.debug('Loading plugin `%s`', plugin)
|
||||
# try to find in namespace plugins
|
||||
if plugin in namespace_plugins:
|
||||
plugin = namespace_plugins[plugin]
|
||||
elif 'pelican.plugins.{}'.format(plugin) in namespace_plugins:
|
||||
plugin = namespace_plugins['pelican.plugins.{}'.format(
|
||||
plugin)]
|
||||
# try to import it
|
||||
else:
|
||||
try:
|
||||
plugin = load_legacy_plugin(
|
||||
plugin,
|
||||
settings.get('PLUGIN_PATHS', []))
|
||||
except ImportError as e:
|
||||
logger.error('Cannot load plugin `%s`\n%s', plugin, e)
|
||||
continue
|
||||
plugins.append(plugin)
|
||||
else:
|
||||
plugins = list(namespace_plugins.values())
|
||||
|
||||
return plugins
|
||||
|
|
@ -27,6 +27,7 @@ article_writer_finalized = signal('article_writer_finalized')
|
|||
|
||||
page_generator_init = signal('page_generator_init')
|
||||
page_generator_finalized = signal('page_generator_finalized')
|
||||
page_generator_write_page = signal('page_generator_write_page')
|
||||
page_writer_finalized = signal('page_writer_finalized')
|
||||
|
||||
static_generator_init = signal('static_generator_init')
|
||||
|
|
@ -1,25 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from html import escape
|
||||
from html.parser import HTMLParser
|
||||
from io import StringIO
|
||||
|
||||
import docutils
|
||||
import docutils.core
|
||||
import docutils.io
|
||||
from docutils.writers.html4css1 import HTMLTranslator
|
||||
|
||||
import six
|
||||
from six.moves.html_parser import HTMLParser
|
||||
from docutils.parsers.rst.languages import get_language as get_docutils_lang
|
||||
from docutils.writers.html4css1 import HTMLTranslator, Writer
|
||||
|
||||
from pelican import rstdirectives # NOQA
|
||||
from pelican import signals
|
||||
from pelican.cache import FileStampDataCacher
|
||||
from pelican.contents import Author, Category, Page, Tag
|
||||
from pelican.utils import SafeDatetime, escape_html, get_date, pelican_open, \
|
||||
posixize_path
|
||||
from pelican.plugins import signals
|
||||
from pelican.utils import get_date, pelican_open, posixize_path
|
||||
|
||||
try:
|
||||
from markdown import Markdown
|
||||
|
|
@ -31,6 +31,20 @@ except ImportError:
|
|||
# This means that _filter_discardable_metadata() must be called on processed
|
||||
# metadata dicts before use, to remove the items with the special value.
|
||||
_DISCARD = object()
|
||||
|
||||
DUPLICATES_DEFINITIONS_ALLOWED = {
|
||||
'tags': False,
|
||||
'date': False,
|
||||
'modified': False,
|
||||
'status': False,
|
||||
'category': False,
|
||||
'author': False,
|
||||
'save_as': False,
|
||||
'url': False,
|
||||
'authors': False,
|
||||
'slug': False
|
||||
}
|
||||
|
||||
METADATA_PROCESSORS = {
|
||||
'tags': lambda x, y: ([
|
||||
Tag(tag, y)
|
||||
|
|
@ -63,7 +77,7 @@ def ensure_metadata_list(text):
|
|||
Regardless, all list items undergo .strip() before returning, and
|
||||
empty items are discarded.
|
||||
"""
|
||||
if isinstance(text, six.text_type):
|
||||
if isinstance(text, str):
|
||||
if ';' in text:
|
||||
text = text.split(';')
|
||||
else:
|
||||
|
|
@ -122,7 +136,7 @@ class BaseReader(object):
|
|||
class _FieldBodyTranslator(HTMLTranslator):
|
||||
|
||||
def __init__(self, document):
|
||||
HTMLTranslator.__init__(self, document)
|
||||
super().__init__(document)
|
||||
self.compact_p = None
|
||||
|
||||
def astext(self):
|
||||
|
|
@ -135,12 +149,19 @@ class _FieldBodyTranslator(HTMLTranslator):
|
|||
pass
|
||||
|
||||
|
||||
def render_node_to_html(document, node):
|
||||
visitor = _FieldBodyTranslator(document)
|
||||
def render_node_to_html(document, node, field_body_translator_class):
|
||||
visitor = field_body_translator_class(document)
|
||||
node.walkabout(visitor)
|
||||
return visitor.astext()
|
||||
|
||||
|
||||
class PelicanHTMLWriter(Writer):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.translator_class = PelicanHTMLTranslator
|
||||
|
||||
|
||||
class PelicanHTMLTranslator(HTMLTranslator):
|
||||
|
||||
def visit_abbreviation(self, node):
|
||||
|
|
@ -160,39 +181,58 @@ class PelicanHTMLTranslator(HTMLTranslator):
|
|||
|
||||
|
||||
class RstReader(BaseReader):
|
||||
"""Reader for reStructuredText files"""
|
||||
"""Reader for reStructuredText files
|
||||
|
||||
By default the output HTML is written using
|
||||
docutils.writers.html4css1.Writer and translated using a subclass of
|
||||
docutils.writers.html4css1.HTMLTranslator. If you want to override it with
|
||||
your own writer/translator (e.g. a HTML5-based one), pass your classes to
|
||||
these two attributes. Look in the source code for details.
|
||||
|
||||
writer_class Used for writing contents
|
||||
field_body_translator_class Used for translating metadata such
|
||||
as article summary
|
||||
|
||||
"""
|
||||
|
||||
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)
|
||||
writer_class = PelicanHTMLWriter
|
||||
field_body_translator_class = _FieldBodyTranslator
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RstReader, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _parse_metadata(self, document):
|
||||
lang_code = self.settings.get('DEFAULT_LANG', 'en')
|
||||
if get_docutils_lang(lang_code):
|
||||
self._language_code = lang_code
|
||||
else:
|
||||
logger.warning("Docutils has no localization for '%s'."
|
||||
" Using 'en' instead.", lang_code)
|
||||
self._language_code = 'en'
|
||||
|
||||
def _parse_metadata(self, document, source_path):
|
||||
"""Return the dict containing document metadata"""
|
||||
formatted_fields = self.settings['FORMATTED_FIELDS']
|
||||
|
||||
output = {}
|
||||
|
||||
if document.first_child_matching_class(docutils.nodes.title) is None:
|
||||
logger.warning(
|
||||
'Document title missing in file %s: '
|
||||
'Ensure exactly one top level section',
|
||||
source_path)
|
||||
|
||||
for docinfo in document.traverse(docutils.nodes.docinfo):
|
||||
for element in docinfo.children:
|
||||
if element.tagname == 'field': # custom fields (e.g. summary)
|
||||
name_elem, body_elem = element.children
|
||||
name = name_elem.astext()
|
||||
if name in formatted_fields:
|
||||
value = render_node_to_html(document, body_elem)
|
||||
value = render_node_to_html(
|
||||
document, body_elem,
|
||||
self.field_body_translator_class)
|
||||
else:
|
||||
value = body_elem.astext()
|
||||
elif element.tagname == 'authors': # author list
|
||||
|
|
@ -210,20 +250,22 @@ class RstReader(BaseReader):
|
|||
extra_params = {'initial_header_level': '2',
|
||||
'syntax_highlight': 'short',
|
||||
'input_encoding': 'utf-8',
|
||||
'exit_status_level': 2,
|
||||
'language_code': self._language_code,
|
||||
'halt_level': 2,
|
||||
'traceback': True,
|
||||
'warning_stream': StringIO(),
|
||||
'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,
|
||||
writer=self.writer_class(),
|
||||
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=source_path)
|
||||
pub.publish(enable_exit_status=True)
|
||||
pub.publish()
|
||||
return pub
|
||||
|
||||
def read(self, source_path):
|
||||
|
|
@ -232,7 +274,7 @@ class RstReader(BaseReader):
|
|||
parts = pub.writer.parts
|
||||
content = parts.get('body')
|
||||
|
||||
metadata = self._parse_metadata(pub.document)
|
||||
metadata = self._parse_metadata(pub.document, source_path)
|
||||
metadata.setdefault('title', parts.get('title'))
|
||||
|
||||
return content, metadata
|
||||
|
|
@ -245,7 +287,7 @@ class MarkdownReader(BaseReader):
|
|||
file_extensions = ['md', 'markdown', 'mkd', 'mdown']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MarkdownReader, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
settings = self.settings['MARKDOWN']
|
||||
settings.setdefault('extension_configs', {})
|
||||
settings.setdefault('extensions', [])
|
||||
|
|
@ -260,6 +302,9 @@ class MarkdownReader(BaseReader):
|
|||
"""Return the dict containing document metadata"""
|
||||
formatted_fields = self.settings['FORMATTED_FIELDS']
|
||||
|
||||
# prevent metadata extraction in fields
|
||||
self._md.preprocessors.deregister('meta')
|
||||
|
||||
output = {}
|
||||
for name, value in meta.items():
|
||||
name = name.lower()
|
||||
|
|
@ -270,7 +315,7 @@ class MarkdownReader(BaseReader):
|
|||
self._md.reset()
|
||||
formatted = self._md.convert(formatted_values)
|
||||
output[name] = self.process_metadata(name, formatted)
|
||||
elif name in METADATA_PROCESSORS:
|
||||
elif not DUPLICATES_DEFINITIONS_ALLOWED.get(name, True):
|
||||
if len(value) > 1:
|
||||
logger.warning(
|
||||
'Duplicate definition of `%s` '
|
||||
|
|
@ -308,11 +353,7 @@ class HTMLReader(BaseReader):
|
|||
|
||||
class _HTMLParser(HTMLParser):
|
||||
def __init__(self, settings, filename):
|
||||
try:
|
||||
# Python 3.4+
|
||||
HTMLParser.__init__(self, convert_charrefs=False)
|
||||
except TypeError:
|
||||
HTMLParser.__init__(self)
|
||||
super().__init__(convert_charrefs=False)
|
||||
self.body = ''
|
||||
self.metadata = {}
|
||||
self.settings = settings
|
||||
|
|
@ -349,7 +390,7 @@ class HTMLReader(BaseReader):
|
|||
if self._in_head:
|
||||
self._in_head = False
|
||||
self._in_top_level = True
|
||||
elif tag == 'title':
|
||||
elif self._in_head and tag == 'title':
|
||||
self._in_title = False
|
||||
self.metadata['title'] = self._data_buffer
|
||||
elif tag == 'body':
|
||||
|
|
@ -357,7 +398,7 @@ class HTMLReader(BaseReader):
|
|||
self._in_body = False
|
||||
self._in_top_level = True
|
||||
elif self._in_body:
|
||||
self._data_buffer += '</{}>'.format(escape_html(tag))
|
||||
self._data_buffer += '</{}>'.format(escape(tag))
|
||||
|
||||
def handle_startendtag(self, tag, attrs):
|
||||
if tag == 'meta' and self._in_head:
|
||||
|
|
@ -378,16 +419,16 @@ class HTMLReader(BaseReader):
|
|||
self._data_buffer += '&#{};'.format(data)
|
||||
|
||||
def build_tag(self, tag, attrs, close_tag):
|
||||
result = '<{}'.format(escape_html(tag))
|
||||
result = '<{}'.format(escape(tag))
|
||||
for k, v in attrs:
|
||||
result += ' ' + escape_html(k)
|
||||
result += ' ' + escape(k)
|
||||
if v is not None:
|
||||
# If the attribute value contains a double quote, surround
|
||||
# with single quotes, otherwise use double quotes.
|
||||
if '"' in v:
|
||||
result += "='{}'".format(escape_html(v, quote=False))
|
||||
result += "='{}'".format(escape(v, quote=False))
|
||||
else:
|
||||
result += '="{}"'.format(escape_html(v, quote=False))
|
||||
result += '="{}"'.format(escape(v, quote=False))
|
||||
if close_tag:
|
||||
return result + ' />'
|
||||
return result + '>'
|
||||
|
|
@ -416,7 +457,17 @@ class HTMLReader(BaseReader):
|
|||
|
||||
if name == 'keywords':
|
||||
name = 'tags'
|
||||
self.metadata[name] = contents
|
||||
|
||||
if name in self.metadata:
|
||||
# if this metadata already exists (i.e. a previous tag with the
|
||||
# same name has already been specified then either convert to
|
||||
# list or append to list
|
||||
if isinstance(self.metadata[name], list):
|
||||
self.metadata[name].append(contents)
|
||||
else:
|
||||
self.metadata[name] = [self.metadata[name], contents]
|
||||
else:
|
||||
self.metadata[name] = contents
|
||||
|
||||
@classmethod
|
||||
def _attr_value(cls, attrs, name, default=None):
|
||||
|
|
@ -475,9 +526,7 @@ class Readers(FileStampDataCacher):
|
|||
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,
|
||||
)
|
||||
super().__init__(settings, cache_name, caching_policy, load_policy)
|
||||
|
||||
@property
|
||||
def extensions(self):
|
||||
|
|
@ -617,10 +666,10 @@ def default_metadata(settings=None, process=None):
|
|||
metadata['category'] = value
|
||||
if settings.get('DEFAULT_DATE', None) and \
|
||||
settings['DEFAULT_DATE'] != 'fs':
|
||||
if isinstance(settings['DEFAULT_DATE'], six.string_types):
|
||||
if isinstance(settings['DEFAULT_DATE'], str):
|
||||
metadata['date'] = get_date(settings['DEFAULT_DATE'])
|
||||
else:
|
||||
metadata['date'] = SafeDatetime(*settings['DEFAULT_DATE'])
|
||||
metadata['date'] = datetime.datetime(*settings['DEFAULT_DATE'])
|
||||
return metadata
|
||||
|
||||
|
||||
|
|
@ -628,10 +677,22 @@ def path_metadata(full_path, source_path, settings=None):
|
|||
metadata = {}
|
||||
if settings:
|
||||
if settings.get('DEFAULT_DATE', None) == 'fs':
|
||||
metadata['date'] = SafeDatetime.fromtimestamp(
|
||||
metadata['date'] = datetime.datetime.fromtimestamp(
|
||||
os.stat(full_path).st_mtime)
|
||||
metadata.update(settings.get('EXTRA_PATH_METADATA', {}).get(
|
||||
source_path, {}))
|
||||
|
||||
# Apply EXTRA_PATH_METADATA for the source path and the paths of any
|
||||
# parent directories. Sorting EPM first ensures that the most specific
|
||||
# path wins conflicts.
|
||||
|
||||
epm = settings.get('EXTRA_PATH_METADATA', {})
|
||||
for path, meta in sorted(epm.items()):
|
||||
# Enforce a trailing slash when checking for parent directories.
|
||||
# This prevents false positives when one file or directory's name
|
||||
# is a prefix of another's.
|
||||
dirpath = os.path.join(path, '')
|
||||
if source_path == path or source_path.startswith(dirpath):
|
||||
metadata.update(meta)
|
||||
|
||||
return metadata
|
||||
|
||||
|
||||
|
|
@ -651,7 +712,7 @@ def parse_path_metadata(source_path, settings=None, process=None):
|
|||
... process=reader.process_metadata)
|
||||
>>> pprint.pprint(metadata) # doctest: +ELLIPSIS
|
||||
{'category': <pelican.urlwrappers.Category object at ...>,
|
||||
'date': SafeDatetime(2013, 1, 1, 0, 0),
|
||||
'date': datetime.datetime(2013, 1, 1, 0, 0),
|
||||
'slug': 'my-slug'}
|
||||
"""
|
||||
metadata = {}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
|
|
@ -10,8 +9,6 @@ from pygments import highlight
|
|||
from pygments.formatters import HtmlFormatter
|
||||
from pygments.lexers import TextLexer, get_lexer_by_name
|
||||
|
||||
import six
|
||||
|
||||
import pelican.settings as pys
|
||||
|
||||
|
||||
|
|
@ -49,7 +46,7 @@ class Pygments(Directive):
|
|||
|
||||
# Fetch the defaults
|
||||
if pys.PYGMENTS_RST_OPTIONS is not None:
|
||||
for k, v in six.iteritems(pys.PYGMENTS_RST_OPTIONS):
|
||||
for k, v in pys.PYGMENTS_RST_OPTIONS.items():
|
||||
# Locally set options overrides the defaults
|
||||
if k not in self.options:
|
||||
self.options[k] = v
|
||||
|
|
|
|||
|
|
@ -1,51 +1,99 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import posixpath
|
||||
import ssl
|
||||
import sys
|
||||
import urllib
|
||||
from http import server
|
||||
|
||||
try:
|
||||
from magic import from_file as magic_from_file
|
||||
except ImportError:
|
||||
magic_from_file = None
|
||||
|
||||
from six.moves import SimpleHTTPServer as srvmod
|
||||
from six.moves import socketserver
|
||||
from pelican.log import init as init_logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler):
|
||||
SUFFIXES = ['', '.html', '/index.html']
|
||||
def parse_arguments():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Pelican Development Server',
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||
)
|
||||
parser.add_argument("port", default=8000, type=int, nargs="?",
|
||||
help="Port to Listen On")
|
||||
parser.add_argument("server", default="", nargs="?",
|
||||
help="Interface to Listen On")
|
||||
parser.add_argument('--ssl', action="store_true",
|
||||
help='Activate SSL listener')
|
||||
parser.add_argument('--cert', default="./cert.pem", nargs="?",
|
||||
help='Path to certificate file. ' +
|
||||
'Relative to current directory')
|
||||
parser.add_argument('--key', default="./key.pem", nargs="?",
|
||||
help='Path to certificate key file. ' +
|
||||
'Relative to current directory')
|
||||
parser.add_argument('--path', default=".",
|
||||
help='Path to pelican source directory to serve. ' +
|
||||
'Relative to current directory')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
class ComplexHTTPRequestHandler(server.SimpleHTTPRequestHandler):
|
||||
SUFFIXES = ['.html', '/index.html', '/', '']
|
||||
|
||||
def translate_path(self, path):
|
||||
# abandon query parameters
|
||||
path = path.split('?', 1)[0]
|
||||
path = path.split('#', 1)[0]
|
||||
# Don't forget explicit trailing slash when normalizing. Issue17324
|
||||
trailing_slash = path.rstrip().endswith('/')
|
||||
path = urllib.parse.unquote(path)
|
||||
path = posixpath.normpath(path)
|
||||
words = path.split('/')
|
||||
words = filter(None, words)
|
||||
path = self.base_path
|
||||
for word in words:
|
||||
if os.path.dirname(word) or word in (os.curdir, os.pardir):
|
||||
# Ignore components that are not a simple file/directory name
|
||||
continue
|
||||
path = os.path.join(path, word)
|
||||
if trailing_slash:
|
||||
path += '/'
|
||||
return path
|
||||
|
||||
def do_GET(self):
|
||||
# cut off a query string
|
||||
if '?' in self.path:
|
||||
self.path, _ = self.path.split('?', 1)
|
||||
original_path = self.path.split('?', 1)[0]
|
||||
# try to find file
|
||||
self.path = self.get_path_that_exists(original_path)
|
||||
|
||||
if not self.path:
|
||||
return
|
||||
|
||||
server.SimpleHTTPRequestHandler.do_GET(self)
|
||||
|
||||
def get_path_that_exists(self, original_path):
|
||||
# Try to strip trailing slash
|
||||
original_path = original_path.rstrip('/')
|
||||
# Try to detect file by applying various suffixes
|
||||
tries = []
|
||||
for suffix in self.SUFFIXES:
|
||||
if not hasattr(self, 'original_path'):
|
||||
self.original_path = self.path
|
||||
|
||||
self.path = self.original_path + suffix
|
||||
path = self.translate_path(self.path)
|
||||
|
||||
if os.path.exists(path):
|
||||
srvmod.SimpleHTTPRequestHandler.do_GET(self)
|
||||
logging.info("Found `%s`." % self.path)
|
||||
break
|
||||
|
||||
logging.info("Tried to find `%s`, but it doesn't exist.",
|
||||
self.path)
|
||||
else:
|
||||
# Fallback if there were no matches
|
||||
logging.warning("Unable to find `%s` or variations.",
|
||||
self.original_path)
|
||||
path = original_path + suffix
|
||||
if os.path.exists(self.translate_path(path)):
|
||||
return path
|
||||
tries.append(path)
|
||||
logger.warning("Unable to find `%s` or variations:\n%s",
|
||||
original_path,
|
||||
'\n'.join(tries))
|
||||
return None
|
||||
|
||||
def guess_type(self, path):
|
||||
"""Guess at the mime type for the specified file.
|
||||
"""
|
||||
mimetype = srvmod.SimpleHTTPRequestHandler.guess_type(self, path)
|
||||
mimetype = server.SimpleHTTPRequestHandler.guess_type(self, path)
|
||||
|
||||
# If the default guess is too generic, try the python-magic library
|
||||
if mimetype == 'application/octet-stream' and magic_from_file:
|
||||
|
|
@ -54,21 +102,39 @@ class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler):
|
|||
return mimetype
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
PORT = len(sys.argv) in (2, 3) and int(sys.argv[1]) or 8000
|
||||
SERVER = len(sys.argv) == 3 and sys.argv[2] or ""
|
||||
class RootedHTTPServer(server.HTTPServer):
|
||||
def __init__(self, base_path, *args, **kwargs):
|
||||
server.HTTPServer.__init__(self, *args, **kwargs)
|
||||
self.RequestHandlerClass.base_path = base_path
|
||||
|
||||
socketserver.TCPServer.allow_reuse_address = True
|
||||
|
||||
if __name__ == '__main__':
|
||||
init_logging(level=logging.INFO)
|
||||
logger.warning("'python -m pelican.server' is deprecated.\nThe "
|
||||
"Pelican development server should be run via "
|
||||
"'pelican --listen' or 'pelican -l'.\nThis can be combined "
|
||||
"with regeneration as 'pelican -lr'.\nRerun 'pelican-"
|
||||
"quickstart' to get new Makefile and tasks.py files.")
|
||||
args = parse_arguments()
|
||||
RootedHTTPServer.allow_reuse_address = True
|
||||
try:
|
||||
httpd = socketserver.TCPServer(
|
||||
(SERVER, PORT), ComplexHTTPRequestHandler)
|
||||
except OSError as e:
|
||||
logging.error("Could not listen on port %s, server %s.", PORT, SERVER)
|
||||
httpd = RootedHTTPServer(
|
||||
args.path, (args.server, args.port), ComplexHTTPRequestHandler)
|
||||
if args.ssl:
|
||||
httpd.socket = ssl.wrap_socket(
|
||||
httpd.socket, keyfile=args.key,
|
||||
certfile=args.cert, server_side=True)
|
||||
except ssl.SSLError as e:
|
||||
logger.error("Couldn't open certificate file %s or key file %s",
|
||||
args.cert, args.key)
|
||||
logger.error("Could not listen on port %s, server %s.",
|
||||
args.port, args.server)
|
||||
sys.exit(getattr(e, 'exitcode', 1))
|
||||
|
||||
logging.info("Serving at port %s, server %s.", PORT, SERVER)
|
||||
logger.info("Serving at port %s, server %s.",
|
||||
args.port, args.server)
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt as e:
|
||||
logging.info("Shutting down server.")
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Shutting down server.")
|
||||
httpd.socket.close()
|
||||
|
|
|
|||
|
|
@ -1,28 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import copy
|
||||
import importlib.util
|
||||
import inspect
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from os.path import isabs
|
||||
from posixpath import join as posix_join
|
||||
|
||||
import six
|
||||
|
||||
from pelican.log import LimitFilter
|
||||
|
||||
try:
|
||||
# SourceFileLoader is the recommended way in 3.3+
|
||||
from importlib.machinery import SourceFileLoader
|
||||
|
||||
def load_source(name, path):
|
||||
return 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
|
||||
def load_source(name, path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -44,10 +39,10 @@ DEFAULT_CONFIG = {
|
|||
'THEME_STATIC_DIR': 'theme',
|
||||
'THEME_STATIC_PATHS': ['static', ],
|
||||
'FEED_ALL_ATOM': posix_join('feeds', 'all.atom.xml'),
|
||||
'CATEGORY_FEED_ATOM': posix_join('feeds', '%s.atom.xml'),
|
||||
'AUTHOR_FEED_ATOM': posix_join('feeds', '%s.atom.xml'),
|
||||
'AUTHOR_FEED_RSS': posix_join('feeds', '%s.rss.xml'),
|
||||
'TRANSLATION_FEED_ATOM': posix_join('feeds', 'all-%s.atom.xml'),
|
||||
'CATEGORY_FEED_ATOM': posix_join('feeds', '{slug}.atom.xml'),
|
||||
'AUTHOR_FEED_ATOM': posix_join('feeds', '{slug}.atom.xml'),
|
||||
'AUTHOR_FEED_RSS': posix_join('feeds', '{slug}.rss.xml'),
|
||||
'TRANSLATION_FEED_ATOM': posix_join('feeds', 'all-{lang}.atom.xml'),
|
||||
'FEED_MAX_ITEMS': '',
|
||||
'RSS_FEED_SUMMARY_ONLY': True,
|
||||
'SITEURL': '',
|
||||
|
|
@ -80,8 +75,15 @@ DEFAULT_CONFIG = {
|
|||
'PAGE_ORDER_BY': 'basename',
|
||||
'PAGE_LANG_URL': 'pages/{slug}-{lang}.html',
|
||||
'PAGE_LANG_SAVE_AS': posix_join('pages', '{slug}-{lang}.html'),
|
||||
'DRAFT_PAGE_URL': 'drafts/pages/{slug}.html',
|
||||
'DRAFT_PAGE_SAVE_AS': posix_join('drafts', 'pages', '{slug}.html'),
|
||||
'DRAFT_PAGE_LANG_URL': 'drafts/pages/{slug}-{lang}.html',
|
||||
'DRAFT_PAGE_LANG_SAVE_AS': posix_join('drafts', 'pages',
|
||||
'{slug}-{lang}.html'),
|
||||
'STATIC_URL': '{path}',
|
||||
'STATIC_SAVE_AS': '{path}',
|
||||
'STATIC_CREATE_LINKS': False,
|
||||
'STATIC_CHECK_IF_MODIFIED': False,
|
||||
'CATEGORY_URL': 'category/{slug}.html',
|
||||
'CATEGORY_SAVE_AS': posix_join('category', '{slug}.html'),
|
||||
'TAG_URL': 'tag/{slug}.html',
|
||||
|
|
@ -89,16 +91,23 @@ DEFAULT_CONFIG = {
|
|||
'AUTHOR_URL': 'author/{slug}.html',
|
||||
'AUTHOR_SAVE_AS': posix_join('author', '{slug}.html'),
|
||||
'PAGINATION_PATTERNS': [
|
||||
(0, '{name}{number}{extension}', '{name}{number}{extension}'),
|
||||
(1, '{name}{extension}', '{name}{extension}'),
|
||||
(2, '{name}{number}{extension}', '{name}{number}{extension}'),
|
||||
],
|
||||
'YEAR_ARCHIVE_URL': '',
|
||||
'YEAR_ARCHIVE_SAVE_AS': '',
|
||||
'MONTH_ARCHIVE_URL': '',
|
||||
'MONTH_ARCHIVE_SAVE_AS': '',
|
||||
'DAY_ARCHIVE_URL': '',
|
||||
'DAY_ARCHIVE_SAVE_AS': '',
|
||||
'RELATIVE_URLS': False,
|
||||
'DEFAULT_LANG': 'en',
|
||||
'ARTICLE_TRANSLATION_ID': 'slug',
|
||||
'PAGE_TRANSLATION_ID': 'slug',
|
||||
'DIRECT_TEMPLATES': ['index', 'tags', 'categories', 'authors', 'archives'],
|
||||
'EXTRA_TEMPLATES_PATHS': [],
|
||||
'PAGINATED_DIRECT_TEMPLATES': ['index'],
|
||||
'THEME_TEMPLATES_OVERRIDES': [],
|
||||
'PAGINATED_TEMPLATES': {'index': None, 'tag': None, 'category': None,
|
||||
'author': None},
|
||||
'PELICAN_CLASS': 'pelican.Pelican',
|
||||
'DEFAULT_DATE_FORMAT': '%a %d %B %Y',
|
||||
'DATE_FORMATS': {},
|
||||
|
|
@ -111,6 +120,8 @@ DEFAULT_CONFIG = {
|
|||
'output_format': 'html5',
|
||||
},
|
||||
'JINJA_FILTERS': {},
|
||||
'JINJA_GLOBALS': {},
|
||||
'JINJA_TESTS': {},
|
||||
'JINJA_ENVIRONMENT': {
|
||||
'trim_blocks': True,
|
||||
'lstrip_blocks': True,
|
||||
|
|
@ -124,17 +135,23 @@ DEFAULT_CONFIG = {
|
|||
'FILENAME_METADATA': r'(?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_END_MARKER': '…',
|
||||
'SUMMARY_MAX_LENGTH': 50,
|
||||
'PLUGIN_PATHS': [],
|
||||
'PLUGINS': [],
|
||||
'PLUGINS': None,
|
||||
'PYGMENTS_RST_OPTIONS': {},
|
||||
'TEMPLATE_PAGES': {},
|
||||
'TEMPLATE_EXTENSIONS': ['.html'],
|
||||
'IGNORE_FILES': ['.#*'],
|
||||
'SLUG_SUBSTITUTIONS': (),
|
||||
'SLUG_REGEX_SUBSTITUTIONS': [
|
||||
(r'[^\w\s-]', ''), # remove non-alphabetical/whitespace/'-' chars
|
||||
(r'(?u)\A\s*', ''), # strip leading whitespace
|
||||
(r'(?u)\s*\Z', ''), # strip trailing whitespace
|
||||
(r'[-\s]+', '-'), # reduce multiple whitespace or '-' to single '-'
|
||||
],
|
||||
'INTRASITE_LINK_REGEX': '[{|](?P<what>.*?)[|}]',
|
||||
'SLUGIFY_SOURCE': 'title',
|
||||
'CACHE_CONTENT': False,
|
||||
|
|
@ -145,85 +162,70 @@ DEFAULT_CONFIG = {
|
|||
'LOAD_CONTENT_CACHE': False,
|
||||
'WRITE_SELECTED': [],
|
||||
'FORMATTED_FIELDS': ['summary'],
|
||||
'PORT': 8000,
|
||||
'BIND': '127.0.0.1',
|
||||
}
|
||||
|
||||
PYGMENTS_RST_OPTIONS = None
|
||||
|
||||
|
||||
def read_settings(path=None, override=None):
|
||||
settings = override or {}
|
||||
|
||||
if path:
|
||||
local_settings = get_settings_from_file(path)
|
||||
# Make the paths relative to the settings file
|
||||
settings = dict(get_settings_from_file(path), **settings)
|
||||
|
||||
if settings:
|
||||
settings = handle_deprecated_settings(settings)
|
||||
|
||||
if path:
|
||||
# Make relative paths absolute
|
||||
def getabs(maybe_relative, base_path=path):
|
||||
if isabs(maybe_relative):
|
||||
return maybe_relative
|
||||
return os.path.abspath(os.path.normpath(os.path.join(
|
||||
os.path.dirname(base_path), maybe_relative)))
|
||||
|
||||
for p in ['PATH', 'OUTPUT_PATH', 'THEME', 'CACHE_PATH']:
|
||||
if p in local_settings and local_settings[p] is not None \
|
||||
and not isabs(local_settings[p]):
|
||||
absp = os.path.abspath(os.path.normpath(os.path.join(
|
||||
os.path.dirname(path), local_settings[p])))
|
||||
if settings.get(p) is not None:
|
||||
absp = getabs(settings[p])
|
||||
# THEME may be a name rather than a path
|
||||
if p != 'THEME' or os.path.exists(absp):
|
||||
local_settings[p] = absp
|
||||
settings[p] = absp
|
||||
|
||||
if 'PLUGIN_PATH' in local_settings:
|
||||
logger.warning('PLUGIN_PATH setting has been replaced by '
|
||||
'PLUGIN_PATHS, moving it to the new setting name.')
|
||||
local_settings['PLUGIN_PATHS'] = local_settings['PLUGIN_PATH']
|
||||
del local_settings['PLUGIN_PATH']
|
||||
if 'JINJA_EXTENSIONS' in local_settings:
|
||||
logger.warning('JINJA_EXTENSIONS setting has been deprecated, '
|
||||
'moving it to JINJA_ENVIRONMENT setting.')
|
||||
local_settings['JINJA_ENVIRONMENT']['extensions'] = \
|
||||
local_settings['JINJA_EXTENSIONS']
|
||||
del local_settings['JINJA_EXTENSIONS']
|
||||
if isinstance(local_settings['PLUGIN_PATHS'], six.string_types):
|
||||
logger.warning("Defining PLUGIN_PATHS setting as string "
|
||||
"has been deprecated (should be a list)")
|
||||
local_settings['PLUGIN_PATHS'] = [local_settings['PLUGIN_PATHS']]
|
||||
elif local_settings['PLUGIN_PATHS'] is not None:
|
||||
def getabs(path, pluginpath):
|
||||
if isabs(pluginpath):
|
||||
return pluginpath
|
||||
else:
|
||||
path_dirname = os.path.dirname(path)
|
||||
path_joined = os.path.join(path_dirname, pluginpath)
|
||||
path_normed = os.path.normpath(path_joined)
|
||||
path_absolute = os.path.abspath(path_normed)
|
||||
return path_absolute
|
||||
if settings.get('PLUGIN_PATHS') is not None:
|
||||
settings['PLUGIN_PATHS'] = [getabs(pluginpath)
|
||||
for pluginpath
|
||||
in settings['PLUGIN_PATHS']]
|
||||
|
||||
pluginpath_list = [getabs(path, pluginpath)
|
||||
for pluginpath
|
||||
in local_settings['PLUGIN_PATHS']]
|
||||
local_settings['PLUGIN_PATHS'] = pluginpath_list
|
||||
else:
|
||||
local_settings = copy.deepcopy(DEFAULT_CONFIG)
|
||||
settings = dict(copy.deepcopy(DEFAULT_CONFIG), **settings)
|
||||
settings = configure_settings(settings)
|
||||
|
||||
if override:
|
||||
local_settings.update(override)
|
||||
|
||||
parsed_settings = configure_settings(local_settings)
|
||||
# This is because there doesn't seem to be a way to pass extra
|
||||
# parameters to docutils directive handlers, so we have to have a
|
||||
# variable here that we'll import from within Pygments.run (see
|
||||
# rstdirectives.py) to see what the user defaults were.
|
||||
global PYGMENTS_RST_OPTIONS
|
||||
PYGMENTS_RST_OPTIONS = parsed_settings.get('PYGMENTS_RST_OPTIONS', None)
|
||||
return parsed_settings
|
||||
PYGMENTS_RST_OPTIONS = settings.get('PYGMENTS_RST_OPTIONS', None)
|
||||
return settings
|
||||
|
||||
|
||||
def get_settings_from_module(module=None, default_settings=DEFAULT_CONFIG):
|
||||
def get_settings_from_module(module=None):
|
||||
"""Loads settings from a module, returns a dictionary."""
|
||||
|
||||
context = copy.deepcopy(default_settings)
|
||||
context = {}
|
||||
if module is not None:
|
||||
context.update(
|
||||
(k, v) for k, v in inspect.getmembers(module) if k.isupper())
|
||||
return context
|
||||
|
||||
|
||||
def get_settings_from_file(path, default_settings=DEFAULT_CONFIG):
|
||||
def get_settings_from_file(path):
|
||||
"""Loads settings from a file path, returning a dict."""
|
||||
|
||||
name, ext = os.path.splitext(os.path.basename(path))
|
||||
module = load_source(name, path)
|
||||
return get_settings_from_module(module, default_settings=default_settings)
|
||||
return get_settings_from_module(module)
|
||||
|
||||
|
||||
def get_jinja_environment(settings):
|
||||
|
|
@ -240,6 +242,263 @@ def get_jinja_environment(settings):
|
|||
return settings
|
||||
|
||||
|
||||
def _printf_s_to_format_field(printf_string, format_field):
|
||||
"""Tries to replace %s with {format_field} in the provided printf_string.
|
||||
Raises ValueError in case of failure.
|
||||
"""
|
||||
TEST_STRING = 'PELICAN_PRINTF_S_DEPRECATION'
|
||||
expected = printf_string % TEST_STRING
|
||||
|
||||
result = printf_string.replace('{', '{{').replace('}', '}}') \
|
||||
% '{{{}}}'.format(format_field)
|
||||
if result.format(**{format_field: TEST_STRING}) != expected:
|
||||
raise ValueError('Failed to safely replace %s with {{{}}}'.format(
|
||||
format_field))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def handle_deprecated_settings(settings):
|
||||
"""Converts deprecated settings and issues warnings. Issues an exception
|
||||
if both old and new setting is specified.
|
||||
"""
|
||||
|
||||
# PLUGIN_PATH -> PLUGIN_PATHS
|
||||
if 'PLUGIN_PATH' in settings:
|
||||
logger.warning('PLUGIN_PATH setting has been replaced by '
|
||||
'PLUGIN_PATHS, moving it to the new setting name.')
|
||||
settings['PLUGIN_PATHS'] = settings['PLUGIN_PATH']
|
||||
del settings['PLUGIN_PATH']
|
||||
|
||||
# PLUGIN_PATHS: str -> [str]
|
||||
if isinstance(settings.get('PLUGIN_PATHS'), str):
|
||||
logger.warning("Defining PLUGIN_PATHS setting as string "
|
||||
"has been deprecated (should be a list)")
|
||||
settings['PLUGIN_PATHS'] = [settings['PLUGIN_PATHS']]
|
||||
|
||||
# JINJA_EXTENSIONS -> JINJA_ENVIRONMENT > extensions
|
||||
if 'JINJA_EXTENSIONS' in settings:
|
||||
logger.warning('JINJA_EXTENSIONS setting has been deprecated, '
|
||||
'moving it to JINJA_ENVIRONMENT setting.')
|
||||
settings['JINJA_ENVIRONMENT']['extensions'] = \
|
||||
settings['JINJA_EXTENSIONS']
|
||||
del settings['JINJA_EXTENSIONS']
|
||||
|
||||
# {ARTICLE,PAGE}_DIR -> {ARTICLE,PAGE}_PATHS
|
||||
for key in ['ARTICLE', 'PAGE']:
|
||||
old_key = key + '_DIR'
|
||||
new_key = key + '_PATHS'
|
||||
if old_key in settings:
|
||||
logger.warning(
|
||||
'Deprecated setting %s, moving it to %s list',
|
||||
old_key, new_key)
|
||||
settings[new_key] = [settings[old_key]] # also make a list
|
||||
del settings[old_key]
|
||||
|
||||
# EXTRA_TEMPLATES_PATHS -> THEME_TEMPLATES_OVERRIDES
|
||||
if 'EXTRA_TEMPLATES_PATHS' in settings:
|
||||
logger.warning('EXTRA_TEMPLATES_PATHS is deprecated use '
|
||||
'THEME_TEMPLATES_OVERRIDES instead.')
|
||||
if ('THEME_TEMPLATES_OVERRIDES' in settings and
|
||||
settings['THEME_TEMPLATES_OVERRIDES']):
|
||||
raise Exception(
|
||||
'Setting both EXTRA_TEMPLATES_PATHS and '
|
||||
'THEME_TEMPLATES_OVERRIDES is not permitted. Please move to '
|
||||
'only setting THEME_TEMPLATES_OVERRIDES.')
|
||||
settings['THEME_TEMPLATES_OVERRIDES'] = \
|
||||
settings['EXTRA_TEMPLATES_PATHS']
|
||||
del settings['EXTRA_TEMPLATES_PATHS']
|
||||
|
||||
# MD_EXTENSIONS -> MARKDOWN
|
||||
if 'MD_EXTENSIONS' in settings:
|
||||
logger.warning('MD_EXTENSIONS is deprecated use MARKDOWN '
|
||||
'instead. Falling back to the default.')
|
||||
settings['MARKDOWN'] = DEFAULT_CONFIG['MARKDOWN']
|
||||
|
||||
# LESS_GENERATOR -> Webassets plugin
|
||||
# FILES_TO_COPY -> STATIC_PATHS, EXTRA_PATH_METADATA
|
||||
for old, new, doc in [
|
||||
('LESS_GENERATOR', 'the Webassets plugin', None),
|
||||
('FILES_TO_COPY', 'STATIC_PATHS and EXTRA_PATH_METADATA',
|
||||
'https://github.com/getpelican/pelican/'
|
||||
'blob/master/docs/settings.rst#path-metadata'),
|
||||
]:
|
||||
if old in settings:
|
||||
message = 'The {} setting has been removed in favor of {}'.format(
|
||||
old, new)
|
||||
if doc:
|
||||
message += ', see {} for details'.format(doc)
|
||||
logger.warning(message)
|
||||
|
||||
# PAGINATED_DIRECT_TEMPLATES -> PAGINATED_TEMPLATES
|
||||
if 'PAGINATED_DIRECT_TEMPLATES' in settings:
|
||||
message = 'The {} setting has been removed in favor of {}'.format(
|
||||
'PAGINATED_DIRECT_TEMPLATES', 'PAGINATED_TEMPLATES')
|
||||
logger.warning(message)
|
||||
|
||||
# set PAGINATED_TEMPLATES
|
||||
if 'PAGINATED_TEMPLATES' not in settings:
|
||||
settings['PAGINATED_TEMPLATES'] = {
|
||||
'tag': None, 'category': None, 'author': None}
|
||||
|
||||
for t in settings['PAGINATED_DIRECT_TEMPLATES']:
|
||||
if t not in settings['PAGINATED_TEMPLATES']:
|
||||
settings['PAGINATED_TEMPLATES'][t] = None
|
||||
del settings['PAGINATED_DIRECT_TEMPLATES']
|
||||
|
||||
# {SLUG,CATEGORY,TAG,AUTHOR}_SUBSTITUTIONS ->
|
||||
# {SLUG,CATEGORY,TAG,AUTHOR}_REGEX_SUBSTITUTIONS
|
||||
url_settings_url = \
|
||||
'http://docs.getpelican.com/en/latest/settings.html#url-settings'
|
||||
flavours = {'SLUG', 'CATEGORY', 'TAG', 'AUTHOR'}
|
||||
old_values = {f: settings[f + '_SUBSTITUTIONS']
|
||||
for f in flavours if f + '_SUBSTITUTIONS' in settings}
|
||||
new_values = {f: settings[f + '_REGEX_SUBSTITUTIONS']
|
||||
for f in flavours if f + '_REGEX_SUBSTITUTIONS' in settings}
|
||||
if old_values and new_values:
|
||||
raise Exception(
|
||||
'Setting both {new_key} and {old_key} (or variants thereof) is '
|
||||
'not permitted. Please move to only setting {new_key}.'
|
||||
.format(old_key='SLUG_SUBSTITUTIONS',
|
||||
new_key='SLUG_REGEX_SUBSTITUTIONS'))
|
||||
if old_values:
|
||||
message = ('{} and variants thereof are deprecated and will be '
|
||||
'removed in the future. Please use {} and variants thereof '
|
||||
'instead. Check {}.'
|
||||
.format('SLUG_SUBSTITUTIONS', 'SLUG_REGEX_SUBSTITUTIONS',
|
||||
url_settings_url))
|
||||
logger.warning(message)
|
||||
if old_values.get('SLUG'):
|
||||
for f in {'CATEGORY', 'TAG'}:
|
||||
if old_values.get(f):
|
||||
old_values[f] = old_values['SLUG'] + old_values[f]
|
||||
old_values['AUTHOR'] = old_values.get('AUTHOR', [])
|
||||
for f in flavours:
|
||||
if old_values.get(f) is not None:
|
||||
regex_subs = []
|
||||
# by default will replace non-alphanum characters
|
||||
replace = True
|
||||
for tpl in old_values[f]:
|
||||
try:
|
||||
src, dst, skip = tpl
|
||||
if skip:
|
||||
replace = False
|
||||
except ValueError:
|
||||
src, dst = tpl
|
||||
regex_subs.append(
|
||||
(re.escape(src), dst.replace('\\', r'\\')))
|
||||
|
||||
if replace:
|
||||
regex_subs += [
|
||||
(r'[^\w\s-]', ''),
|
||||
(r'(?u)\A\s*', ''),
|
||||
(r'(?u)\s*\Z', ''),
|
||||
(r'[-\s]+', '-'),
|
||||
]
|
||||
else:
|
||||
regex_subs += [
|
||||
(r'(?u)\A\s*', ''),
|
||||
(r'(?u)\s*\Z', ''),
|
||||
]
|
||||
settings[f + '_REGEX_SUBSTITUTIONS'] = regex_subs
|
||||
settings.pop(f + '_SUBSTITUTIONS', None)
|
||||
|
||||
# `%s` -> '{slug}` or `{lang}` in FEED settings
|
||||
for key in ['TRANSLATION_FEED_ATOM',
|
||||
'TRANSLATION_FEED_RSS'
|
||||
]:
|
||||
if settings.get(key) and '%s' in settings[key]:
|
||||
logger.warning('%%s usage in %s is deprecated, use {lang} '
|
||||
'instead.', key)
|
||||
try:
|
||||
settings[key] = _printf_s_to_format_field(
|
||||
settings[key], 'lang')
|
||||
except ValueError:
|
||||
logger.warning('Failed to convert %%s to {lang} for %s. '
|
||||
'Falling back to default.', key)
|
||||
settings[key] = DEFAULT_CONFIG[key]
|
||||
for key in ['AUTHOR_FEED_ATOM',
|
||||
'AUTHOR_FEED_RSS',
|
||||
'CATEGORY_FEED_ATOM',
|
||||
'CATEGORY_FEED_RSS',
|
||||
'TAG_FEED_ATOM',
|
||||
'TAG_FEED_RSS',
|
||||
]:
|
||||
if settings.get(key) and '%s' in settings[key]:
|
||||
logger.warning('%%s usage in %s is deprecated, use {slug} '
|
||||
'instead.', key)
|
||||
try:
|
||||
settings[key] = _printf_s_to_format_field(
|
||||
settings[key], 'slug')
|
||||
except ValueError:
|
||||
logger.warning('Failed to convert %%s to {slug} for %s. '
|
||||
'Falling back to default.', key)
|
||||
settings[key] = DEFAULT_CONFIG[key]
|
||||
|
||||
# CLEAN_URLS
|
||||
if settings.get('CLEAN_URLS', False):
|
||||
logger.warning('Found deprecated `CLEAN_URLS` in settings.'
|
||||
' Modifying the following settings for the'
|
||||
' same behaviour.')
|
||||
|
||||
settings['ARTICLE_URL'] = '{slug}/'
|
||||
settings['ARTICLE_LANG_URL'] = '{slug}-{lang}/'
|
||||
settings['PAGE_URL'] = 'pages/{slug}/'
|
||||
settings['PAGE_LANG_URL'] = 'pages/{slug}-{lang}/'
|
||||
|
||||
for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL',
|
||||
'PAGE_LANG_URL'):
|
||||
logger.warning("%s = '%s'", setting, settings[setting])
|
||||
|
||||
# AUTORELOAD_IGNORE_CACHE -> --ignore-cache
|
||||
if settings.get('AUTORELOAD_IGNORE_CACHE'):
|
||||
logger.warning('Found deprecated `AUTORELOAD_IGNORE_CACHE` in '
|
||||
'settings. Use --ignore-cache instead.')
|
||||
settings.pop('AUTORELOAD_IGNORE_CACHE')
|
||||
|
||||
# ARTICLE_PERMALINK_STRUCTURE
|
||||
if settings.get('ARTICLE_PERMALINK_STRUCTURE', False):
|
||||
logger.warning('Found deprecated `ARTICLE_PERMALINK_STRUCTURE` in'
|
||||
' settings. Modifying the following settings for'
|
||||
' the same behaviour.')
|
||||
|
||||
structure = settings['ARTICLE_PERMALINK_STRUCTURE']
|
||||
|
||||
# Convert %(variable) into {variable}.
|
||||
structure = re.sub(r'%\((\w+)\)s', r'{\g<1>}', structure)
|
||||
|
||||
# Convert %x into {date:%x} for strftime
|
||||
structure = re.sub(r'(%[A-z])', r'{date:\g<1>}', structure)
|
||||
|
||||
# Strip a / prefix
|
||||
structure = re.sub('^/', '', structure)
|
||||
|
||||
for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL',
|
||||
'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'):
|
||||
settings[setting] = os.path.join(structure,
|
||||
settings[setting])
|
||||
logger.warning("%s = '%s'", setting, settings[setting])
|
||||
|
||||
# {,TAG,CATEGORY,TRANSLATION}_FEED -> {,TAG,CATEGORY,TRANSLATION}_FEED_ATOM
|
||||
for new, old in [('FEED', 'FEED_ATOM'), ('TAG_FEED', 'TAG_FEED_ATOM'),
|
||||
('CATEGORY_FEED', 'CATEGORY_FEED_ATOM'),
|
||||
('TRANSLATION_FEED', 'TRANSLATION_FEED_ATOM')]:
|
||||
if 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}
|
||||
)
|
||||
settings[old] = settings[new]
|
||||
|
||||
return settings
|
||||
|
||||
|
||||
def configure_settings(settings):
|
||||
"""Provide optimizations, error checking, and warnings for the given
|
||||
settings.
|
||||
|
|
@ -281,13 +540,13 @@ def configure_settings(settings):
|
|||
|
||||
# standardize strings to lists
|
||||
for key in ['LOCALE']:
|
||||
if key in settings and isinstance(settings[key], six.string_types):
|
||||
if key in settings and isinstance(settings[key], str):
|
||||
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),
|
||||
('OUTPUT_SOURCES_EXTENSION', str),
|
||||
('FILENAME_METADATA', str),
|
||||
]:
|
||||
if key in settings and not isinstance(settings[key], types):
|
||||
value = settings.pop(key)
|
||||
|
|
@ -323,11 +582,11 @@ def configure_settings(settings):
|
|||
|
||||
# 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', False):
|
||||
logger.warning(
|
||||
"WITH_FUTURE_DATES conflicts with CONTENT_CACHING_LAYER "
|
||||
"set to 'generator', use 'reader' layer instead")
|
||||
settings.get('CONTENT_CACHING_LAYER', '') == 'generator' and \
|
||||
settings.get('WITH_FUTURE_DATES', False):
|
||||
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
|
||||
feed_keys = [
|
||||
|
|
@ -364,23 +623,12 @@ def configure_settings(settings):
|
|||
key=lambda r: r[0],
|
||||
)
|
||||
|
||||
# move {ARTICLE,PAGE}_DIR -> {ARTICLE,PAGE}_PATHS
|
||||
for key in ['ARTICLE', 'PAGE']:
|
||||
old_key = key + '_DIR'
|
||||
new_key = key + '_PATHS'
|
||||
if old_key in settings:
|
||||
logger.warning(
|
||||
'Deprecated setting %s, moving it to %s list',
|
||||
old_key, new_key)
|
||||
settings[new_key] = [settings[old_key]] # also make a list
|
||||
del settings[old_key]
|
||||
|
||||
# Save people from accidentally setting a string rather than a list
|
||||
path_keys = (
|
||||
'ARTICLE_EXCLUDES',
|
||||
'DEFAULT_METADATA',
|
||||
'DIRECT_TEMPLATES',
|
||||
'EXTRA_TEMPLATES_PATHS',
|
||||
'THEME_TEMPLATES_OVERRIDES',
|
||||
'FILES_TO_COPY',
|
||||
'IGNORE_FILES',
|
||||
'PAGINATED_DIRECT_TEMPLATES',
|
||||
|
|
@ -392,18 +640,12 @@ def configure_settings(settings):
|
|||
'PAGE_PATHS',
|
||||
)
|
||||
for PATH_KEY in filter(lambda k: k in settings, path_keys):
|
||||
if isinstance(settings[PATH_KEY], six.string_types):
|
||||
if isinstance(settings[PATH_KEY], str):
|
||||
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]
|
||||
|
||||
# Deprecated warning of MD_EXTENSIONS
|
||||
if 'MD_EXTENSIONS' in settings:
|
||||
logger.warning('MD_EXTENSIONS is deprecated use MARKDOWN '
|
||||
'instead. Falling back to the default.')
|
||||
settings['MARKDOWN'] = DEFAULT_CONFIG['MARKDOWN']
|
||||
|
||||
# Add {PAGE,ARTICLE}_PATHS to {ARTICLE,PAGE}_EXCLUDES
|
||||
mutually_exclusive = ('ARTICLE', 'PAGE')
|
||||
for type_1, type_2 in [mutually_exclusive, mutually_exclusive[::-1]]:
|
||||
|
|
@ -416,17 +658,4 @@ def configure_settings(settings):
|
|||
except KeyError:
|
||||
continue # setting not specified, nothing to do
|
||||
|
||||
for old, new, doc in [
|
||||
('LESS_GENERATOR', 'the Webassets plugin', None),
|
||||
('FILES_TO_COPY', 'STATIC_PATHS and EXTRA_PATH_METADATA',
|
||||
'https://github.com/getpelican/pelican/'
|
||||
'blob/master/docs/settings.rst#path-metadata'),
|
||||
]:
|
||||
if old in settings:
|
||||
message = 'The {} setting has been removed in favor of {}'.format(
|
||||
old, new)
|
||||
if doc:
|
||||
message += ', see {} for details'.format(doc)
|
||||
logger.warning(message)
|
||||
|
||||
return settings
|
||||
|
|
|
|||
8
pelican/tests/TestPages/draft_page.rst
Normal file
8
pelican/tests/TestPages/draft_page.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
This is a test draft page
|
||||
##########################
|
||||
|
||||
:status: draft
|
||||
|
||||
The quick brown fox .
|
||||
|
||||
This page is a draft.
|
||||
12
pelican/tests/TestPages/draft_page_markdown.md
Normal file
12
pelican/tests/TestPages/draft_page_markdown.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
title: This is a markdown test draft page
|
||||
status: draft
|
||||
|
||||
Test Markdown File Header
|
||||
=========================
|
||||
|
||||
Used for pelican test
|
||||
---------------------
|
||||
|
||||
The quick brown fox .
|
||||
|
||||
This page is a draft
|
||||
11
pelican/tests/TestPages/draft_page_with_template.rst
Normal file
11
pelican/tests/TestPages/draft_page_with_template.rst
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
This is a test draft page with a custom template
|
||||
#################################################
|
||||
|
||||
:status: draft
|
||||
:template: custom
|
||||
|
||||
The quick brown fox .
|
||||
|
||||
This page is a draft
|
||||
|
||||
This page has a custom template to be called when rendered
|
||||
7
pelican/tests/TestPages/page_with_static_links.md
Normal file
7
pelican/tests/TestPages/page_with_static_links.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
Title: Page with static links
|
||||
|
||||
My links:
|
||||
|
||||
[Link 0]({static}image0.jpg)
|
||||
|
||||
[Link 1]({attach}image1.jpg)
|
||||
17
pelican/tests/content/article_with_inline_svg.html
vendored
Normal file
17
pelican/tests/content/article_with_inline_svg.html
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Article with an inline SVG</title>
|
||||
</head>
|
||||
<body>
|
||||
Ensure that the title attribute in an inline svg is not handled as an HTML title.
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="210mm" height="297mm" viewBox="0 0 210 297">
|
||||
<path fill="#b2b2ff" stroke="#000" stroke-width="2.646" d="M88.698 89.869l-8.899 15.63a38.894 38.894 0 00-16.474 31.722 38.894 38.894 0 0038.894 38.894 38.894 38.894 0 0038.894-38.894 38.894 38.894 0 00-9-24.83l-2.38-16.886-14.828 4.994a38.894 38.894 0 00-12.13-2.144z">
|
||||
<title>A different title inside the inline SVG</title>
|
||||
</path>
|
||||
<ellipse cx="100.806" cy="125.285" rx="3.704" ry="10.583"/>
|
||||
<ellipse cx="82.021" cy="125.285" rx="3.704" ry="10.583"/>
|
||||
<ellipse cx="-111.432" cy="146.563" rx="3.704" ry="10.583" transform="rotate(-64.822)"/>
|
||||
<ellipse cx="-118.245" cy="91.308" rx="6.18" ry="8.62" transform="matrix(.063 -.99801 .96163 .27436 0 0)"/>
|
||||
</svg>
|
||||
</body>
|
||||
</html>
|
||||
5
pelican/tests/content/article_with_markdown_and_nested_metadata.md
vendored
Normal file
5
pelican/tests/content/article_with_markdown_and_nested_metadata.md
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
Title: Article with markdown and nested summary metadata
|
||||
Date: 2012-10-30
|
||||
Summary: Test: This metadata value looks like metadata
|
||||
|
||||
This is some content.
|
||||
11
pelican/tests/content/article_with_multiple_metadata_tags.html
vendored
Normal file
11
pelican/tests/content/article_with_multiple_metadata_tags.html
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Metadata tags as list!</title>
|
||||
<meta name="custom_field" content="https://getpelican.com" />
|
||||
<meta name="custom_field" content="https://www.eff.org" />
|
||||
</head>
|
||||
<body>
|
||||
When custom metadata tags are specified more than once
|
||||
they are collected into a list!
|
||||
</body>
|
||||
</html>
|
||||
1067
pelican/tests/content/bloggerexport.xml
vendored
Normal file
1067
pelican/tests/content/bloggerexport.xml
vendored
Normal file
File diff suppressed because it is too large
Load diff
6
pelican/tests/content/wordpressexport.xml
vendored
6
pelican/tests/content/wordpressexport.xml
vendored
|
|
@ -554,7 +554,11 @@ Pelicans are supposed to eat fish, damn it!
|
|||
|
||||
<iframe width="420" height="315" src="http://www.youtube.com/embed/QNNl_uWmQXE" frameborder="0" allowfullscreen></iframe>
|
||||
|
||||
Bottom line: don't mess up with birds]]></content:encoded>
|
||||
Bottom line: don't mess up with birds
|
||||
|
||||
"That's a 'wonderful' shoe."
|
||||
|
||||
“That’s a ‘magic’ sock.”]]></content:encoded>
|
||||
<excerpt:encoded><![CDATA[]]></excerpt:encoded>
|
||||
<wp:post_id>173</wp:post_id>
|
||||
<wp:post_date>2012-02-16 15:52:55</wp:post_date>
|
||||
|
|
|
|||
7
pelican/tests/cyclic_intersite_links/first-article.rst
Normal file
7
pelican/tests/cyclic_intersite_links/first-article.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
First article
|
||||
#############
|
||||
|
||||
:date: 2018-11-10
|
||||
:summary: Here's the `second <{filename}/second-article.rst>`_,
|
||||
`third <{filename}/third-article.rst>`_ and a
|
||||
`nonexistent article <{filename}/nonexistent.rst>`_.
|
||||
7
pelican/tests/cyclic_intersite_links/second-article.rst
Normal file
7
pelican/tests/cyclic_intersite_links/second-article.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
Second article
|
||||
##############
|
||||
|
||||
:date: 2018-11-10
|
||||
:summary: Here's the `first <{filename}/first-article.rst>`_,
|
||||
`third <{filename}/third-article.rst>`_ and a
|
||||
`nonexistent article <{filename}/nonexistent.rst>`_.
|
||||
7
pelican/tests/cyclic_intersite_links/third-article.rst
Normal file
7
pelican/tests/cyclic_intersite_links/third-article.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
Third article
|
||||
#############
|
||||
|
||||
:date: 2018-11-10
|
||||
:summary: Here's the `first <{filename}/first-article.rst>`_,
|
||||
`second <{filename}/second-article.rst>`_ and a
|
||||
`nonexistent article <{filename}/nonexistent.rst>`_.
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function, unicode_literals
|
||||
AUTHOR = 'Alexis Métaireau'
|
||||
SITENAME = "Alexis' log"
|
||||
SITEURL = 'http://blog.notmyidea.org'
|
||||
|
|
@ -12,7 +11,7 @@ REVERSE_CATEGORY_ORDER = True
|
|||
DEFAULT_PAGINATION = 2
|
||||
|
||||
FEED_RSS = 'feeds/all.rss.xml'
|
||||
CATEGORY_FEED_RSS = 'feeds/%s.rss.xml'
|
||||
CATEGORY_FEED_RSS = 'feeds/{slug}.rss.xml'
|
||||
|
||||
LINKS = (('Biologeek', 'http://biologeek.org'),
|
||||
('Filyb', "http://filyb.info/"),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
NAME = 'namespace plugin'
|
||||
|
||||
|
||||
def register():
|
||||
pass
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
NAME = 'normal plugin'
|
||||
|
||||
|
||||
def register():
|
||||
pass
|
||||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>A markdown powered article</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -59,10 +56,10 @@
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>A Pelican Blog</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -62,10 +59,10 @@
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>Article 1</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -58,10 +55,10 @@
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>Article 2</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -58,10 +55,10 @@
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>Article 3</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -58,10 +55,10 @@
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>A Pelican Blog - Alexis Métaireau</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -46,8 +43,8 @@
|
|||
<div class="section" id="this-is-a-simple-title">
|
||||
<h2>This is a simple title</h2>
|
||||
<p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p>
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
|
||||
<pre class="literal-block">
|
||||
>>> from ipdb import set_trace
|
||||
>>> set_trace()
|
||||
|
|
@ -77,12 +74,14 @@
|
|||
By <a class="url fn" href="/author/alexis-metaireau.html">Alexis Métaireau</a>
|
||||
</address>
|
||||
<p>In <a href="/category/bar.html">bar</a>.</p>
|
||||
<p>tags: <a href="/tag/oh.html">oh</a> <a href="/tag/bar.html">bar</a> <a href="/tag/yeah.html">yeah</a> </p>
|
||||
<p>tags: <a href="/tag/oh.html">oh</a> <a href="/tag/bar.html">bar</a> <a href="/tag/yeah.html">yeah</a> </p>Translations:
|
||||
<a href="/oh-yeah-fr.html" hreflang="fr">fr</a>
|
||||
|
||||
</footer><!-- /.post-info --> <div class="section" id="why-not">
|
||||
<h2>Why not ?</h2>
|
||||
<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
|
||||
YEAH !</p>
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
</div>
|
||||
|
||||
<a class="readmore" href="/oh-yeah.html">read more</a>
|
||||
|
|
@ -102,10 +101,10 @@ YEAH !</p>
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>A Pelican Blog - Authors</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -44,10 +41,10 @@
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>A Pelican Blog</title>
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>A Pelican Blog - Categories</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -24,12 +21,13 @@
|
|||
<li><a href="/category/yeah.html">yeah</a></li>
|
||||
</ul></nav>
|
||||
</header><!-- /#banner -->
|
||||
<ul>
|
||||
<li><a href="/category/bar.html">bar</a></li>
|
||||
<li><a href="/category/cat1.html">cat1</a></li>
|
||||
<li><a href="/category/misc.html">misc</a></li>
|
||||
<li><a href="/category/yeah.html">yeah</a></li>
|
||||
</ul>
|
||||
<h1>Categories on A Pelican Blog</h1>
|
||||
<ul>
|
||||
<li><a href="/category/bar.html">bar</a> (1)</li>
|
||||
<li><a href="/category/cat1.html">cat1</a> (4)</li>
|
||||
<li><a href="/category/misc.html">misc</a> (4)</li>
|
||||
<li><a href="/category/yeah.html">yeah</a> (1)</li>
|
||||
</ul>
|
||||
<section id="extras" class="body">
|
||||
<div class="social">
|
||||
<h2>social</h2>
|
||||
|
|
@ -42,10 +40,10 @@
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>A Pelican Blog - bar</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -37,12 +34,14 @@
|
|||
By <a class="url fn" href="/author/alexis-metaireau.html">Alexis Métaireau</a>
|
||||
</address>
|
||||
<p>In <a href="/category/bar.html">bar</a>.</p>
|
||||
<p>tags: <a href="/tag/oh.html">oh</a> <a href="/tag/bar.html">bar</a> <a href="/tag/yeah.html">yeah</a> </p>
|
||||
<p>tags: <a href="/tag/oh.html">oh</a> <a href="/tag/bar.html">bar</a> <a href="/tag/yeah.html">yeah</a> </p>Translations:
|
||||
<a href="/oh-yeah-fr.html" hreflang="fr">fr</a>
|
||||
|
||||
</footer><!-- /.post-info --><div class="section" id="why-not">
|
||||
<h2>Why not ?</h2>
|
||||
<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
|
||||
YEAH !</p>
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
</div>
|
||||
</article>
|
||||
</aside><!-- /#featured -->
|
||||
|
|
@ -58,10 +57,10 @@ YEAH !</p>
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>A Pelican Blog - cat1</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -117,10 +114,10 @@
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>A Pelican Blog - misc</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -57,7 +54,7 @@
|
|||
|
||||
<p>In <a href="/category/misc.html">misc</a>.</p>
|
||||
<p>tags: <a href="/tag/foo.html">foo</a> <a href="/tag/bar.html">bar</a> <a href="/tag/baz.html">baz</a> </p>Translations:
|
||||
<a href="/second-article-fr.html">fr</a>
|
||||
<a href="/second-article-fr.html" hreflang="fr">fr</a>
|
||||
|
||||
</footer><!-- /.post-info --> <p>This is some article, in english</p>
|
||||
|
||||
|
|
@ -128,10 +125,10 @@ pelican.conf, it will …</p></div>
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>A Pelican Blog - yeah</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -46,8 +43,8 @@
|
|||
<div class="section" id="this-is-a-simple-title">
|
||||
<h2>This is a simple title</h2>
|
||||
<p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p>
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
|
||||
<pre class="literal-block">
|
||||
>>> from ipdb import set_trace
|
||||
>>> set_trace()
|
||||
|
|
@ -68,10 +65,10 @@
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>A draft article without date</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
<li><a href="/pages/this-is-a-test-page.html">This is a test page</a></li>
|
||||
<li><a href="/category/bar.html">bar</a></li>
|
||||
<li><a href="/category/cat1.html">cat1</a></li>
|
||||
<li class="active"><a href="/category/misc.html">misc</a></li>
|
||||
<li><a href="/category/yeah.html">yeah</a></li>
|
||||
</ul></nav>
|
||||
</header><!-- /#banner -->
|
||||
<section id="content" class="body">
|
||||
<article>
|
||||
<header>
|
||||
<h1 class="entry-title">
|
||||
<a href="/drafts/a-draft-article-without-date.html" rel="bookmark"
|
||||
title="Permalink to A draft article without date">A draft article without date</a></h1>
|
||||
</header>
|
||||
|
||||
<div class="entry-content">
|
||||
<footer class="post-info">
|
||||
<abbr class="published" title="9999-12-31T23:59:59.999999+00:00">
|
||||
Published:
|
||||
</abbr>
|
||||
|
||||
<p>In <a href="/category/misc.html">misc</a>.</p>
|
||||
|
||||
</footer><!-- /.post-info --> <p>This is a draft article, it should live under the /drafts/ folder and not be
|
||||
listed anywhere else.</p>
|
||||
|
||||
</div><!-- /.entry-content -->
|
||||
|
||||
</article>
|
||||
</section>
|
||||
<section id="extras" class="body">
|
||||
<div class="social">
|
||||
<h2>social</h2>
|
||||
<ul>
|
||||
<li><a href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate">atom feed</a></li>
|
||||
|
||||
</ul>
|
||||
</div><!-- /.social -->
|
||||
</section><!-- /#extras -->
|
||||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
66
pelican/tests/output/basic/drafts/a-draft-article.html
Normal file
66
pelican/tests/output/basic/drafts/a-draft-article.html
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>A draft article</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
<li><a href="/pages/this-is-a-test-page.html">This is a test page</a></li>
|
||||
<li><a href="/category/bar.html">bar</a></li>
|
||||
<li><a href="/category/cat1.html">cat1</a></li>
|
||||
<li class="active"><a href="/category/misc.html">misc</a></li>
|
||||
<li><a href="/category/yeah.html">yeah</a></li>
|
||||
</ul></nav>
|
||||
</header><!-- /#banner -->
|
||||
<section id="content" class="body">
|
||||
<article>
|
||||
<header>
|
||||
<h1 class="entry-title">
|
||||
<a href="/drafts/a-draft-article.html" rel="bookmark"
|
||||
title="Permalink to A draft article">A draft article</a></h1>
|
||||
</header>
|
||||
|
||||
<div class="entry-content">
|
||||
<footer class="post-info">
|
||||
<abbr class="published" title="2011-05-08T15:58:00+00:00">
|
||||
Published: Sun 08 May 2011
|
||||
</abbr>
|
||||
|
||||
<p>In <a href="/category/misc.html">misc</a>.</p>
|
||||
|
||||
</footer><!-- /.post-info --> <p>This is a draft article, it should live under the /drafts/ folder and not be
|
||||
listed anywhere else.</p>
|
||||
|
||||
</div><!-- /.entry-content -->
|
||||
|
||||
</article>
|
||||
</section>
|
||||
<section id="extras" class="body">
|
||||
<div class="social">
|
||||
<h2>social</h2>
|
||||
<ul>
|
||||
<li><a href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate">atom feed</a></li>
|
||||
|
||||
</ul>
|
||||
</div><!-- /.social -->
|
||||
</section><!-- /#extras -->
|
||||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -5,24 +5,18 @@ as well as <strong>inline markup</strong>.</p>
|
|||
<div class="section" id="this-is-a-simple-title">
|
||||
<h2>This is a simple title</h2>
|
||||
<p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p>
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
|
||||
<pre class="literal-block">
|
||||
&gt;&gt;&gt; from ipdb import set_trace
|
||||
&gt;&gt;&gt; set_trace()
|
||||
</pre>
|
||||
<p>→ And now try with some utf8 hell: ééé</p>
|
||||
</div>
|
||||
</content><category term="foo"></category><category term="bar"></category><category term="foobar"></category></entry><entry><title>Oh yeah !</title><link href="/oh-yeah.html" rel="alternate"></link><published>2010-10-20T10:14:00+00:00</published><updated>2010-10-20T10:14:00+00:00</updated><author><name>Alexis Métaireau</name></author><id>tag:None,2010-10-20:/oh-yeah.html</id><summary type="html"><div class="section" id="why-not">
|
||||
</content><category term="yeah"></category><category term="foo"></category><category term="bar"></category><category term="foobar"></category></entry><entry><title>Oh yeah !</title><link href="/oh-yeah.html" rel="alternate"></link><published>2010-10-20T10:14:00+00:00</published><updated>2010-10-20T10:14:00+00:00</updated><author><name>Alexis Métaireau</name></author><id>tag:None,2010-10-20:/oh-yeah.html</id><content type="html"><div class="section" id="why-not">
|
||||
<h2>Why not ?</h2>
|
||||
<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
|
||||
YEAH !</p>
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
</div>
|
||||
</summary><content type="html"><div class="section" id="why-not">
|
||||
<h2>Why not ?</h2>
|
||||
<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
|
||||
YEAH !</p>
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
</div>
|
||||
</content><category term="oh"></category><category term="bar"></category><category term="yeah"></category></entry></feed>
|
||||
</content><category term="bar"></category><category term="oh"></category><category term="bar"></category><category term="yeah"></category></entry></feed>
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rss version="2.0"><channel><title>A Pelican Blog - Alexis Métaireau</title><link>/</link><description></description><lastBuildDate>Sun, 17 Nov 2013 23:29:00 +0000</lastBuildDate><item><title>This is a super article !</title><link>/this-is-a-super-article.html</link><description><p class="first last">Multi-line metadata should be supported
|
||||
as well as <strong>inline markup</strong>.</p>
|
||||
</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Alexis Métaireau</dc:creator><pubDate>Thu, 02 Dec 2010 10:14:00 +0000</pubDate><guid isPermaLink="false">tag:None,2010-12-02:/this-is-a-super-article.html</guid><category>foo</category><category>bar</category><category>foobar</category></item><item><title>Oh yeah !</title><link>/oh-yeah.html</link><description><div class="section" id="why-not">
|
||||
</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Alexis Métaireau</dc:creator><pubDate>Thu, 02 Dec 2010 10:14:00 +0000</pubDate><guid isPermaLink="false">tag:None,2010-12-02:/this-is-a-super-article.html</guid><category>yeah</category><category>foo</category><category>bar</category><category>foobar</category></item><item><title>Oh yeah !</title><link>/oh-yeah.html</link><description><div class="section" id="why-not">
|
||||
<h2>Why not ?</h2>
|
||||
<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
|
||||
YEAH !</p>
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
</div>
|
||||
</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Alexis Métaireau</dc:creator><pubDate>Wed, 20 Oct 2010 10:14:00 +0000</pubDate><guid isPermaLink="false">tag:None,2010-10-20:/oh-yeah.html</guid><category>oh</category><category>bar</category><category>yeah</category></item></channel></rss>
|
||||
</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Alexis Métaireau</dc:creator><pubDate>Wed, 20 Oct 2010 10:14:00 +0000</pubDate><guid isPermaLink="false">tag:None,2010-10-20:/oh-yeah.html</guid><category>bar</category><category>oh</category><category>bar</category><category>yeah</category></item></channel></rss>
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom"><title>A Pelican Blog</title><link href="/" rel="alternate"></link><link href="/feeds/all-fr.atom.xml" rel="self"></link><id>/</id><updated>2012-02-29T00:00:00+00:00</updated><entry><title>Deuxième article</title><link href="/second-article-fr.html" rel="alternate"></link><published>2012-02-29T00:00:00+00:00</published><updated>2012-02-29T00:00:00+00:00</updated><author><name></name></author><id>tag:None,2012-02-29:/second-article-fr.html</id><summary type="html"><p>Ceci est un article, en français.</p>
|
||||
</summary><content type="html"><p>Ceci est un article, en français.</p>
|
||||
</content><category term="foo"></category><category term="bar"></category><category term="baz"></category></entry></feed>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom"><title>A Pelican Blog</title><link href="/" rel="alternate"></link><link href="/feeds/all-fr.atom.xml" rel="self"></link><id>/</id><updated>2012-02-29T00:00:00+00:00</updated><entry><title>Deuxième article</title><link href="/second-article-fr.html" rel="alternate"></link><published>2012-02-29T00:00:00+00:00</published><updated>2012-02-29T00:00:00+00:00</updated><author><name></name></author><id>tag:None,2012-02-29:/second-article-fr.html</id><content type="html"><p>Ceci est un article, en français.</p>
|
||||
</content><category term="misc"></category><category term="foo"></category><category term="bar"></category><category term="baz"></category></entry><entry><title>Trop bien !</title><link href="/oh-yeah-fr.html" rel="alternate"></link><published>2010-10-20T10:14:00+00:00</published><updated>2010-10-20T10:14:00+00:00</updated><author><name></name></author><id>tag:None,2010-10-20:/oh-yeah-fr.html</id><content type="html"><p>Et voila du contenu en français</p>
|
||||
</content><category term="misc"></category></entry></feed>
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,14 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom"><title>A Pelican Blog - bar</title><link href="/" rel="alternate"></link><link href="/feeds/bar.atom.xml" rel="self"></link><id>/</id><updated>2010-10-20T10:14:00+00:00</updated><entry><title>Oh yeah !</title><link href="/oh-yeah.html" rel="alternate"></link><published>2010-10-20T10:14:00+00:00</published><updated>2010-10-20T10:14:00+00:00</updated><author><name>Alexis Métaireau</name></author><id>tag:None,2010-10-20:/oh-yeah.html</id><summary type="html"><div class="section" id="why-not">
|
||||
<feed xmlns="http://www.w3.org/2005/Atom"><title>A Pelican Blog - bar</title><link href="/" rel="alternate"></link><link href="/feeds/bar.atom.xml" rel="self"></link><id>/</id><updated>2010-10-20T10:14:00+00:00</updated><entry><title>Oh yeah !</title><link href="/oh-yeah.html" rel="alternate"></link><published>2010-10-20T10:14:00+00:00</published><updated>2010-10-20T10:14:00+00:00</updated><author><name>Alexis Métaireau</name></author><id>tag:None,2010-10-20:/oh-yeah.html</id><content type="html"><div class="section" id="why-not">
|
||||
<h2>Why not ?</h2>
|
||||
<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
|
||||
YEAH !</p>
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
</div>
|
||||
</summary><content type="html"><div class="section" id="why-not">
|
||||
<h2>Why not ?</h2>
|
||||
<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
|
||||
YEAH !</p>
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
</div>
|
||||
</content><category term="oh"></category><category term="bar"></category><category term="yeah"></category></entry></feed>
|
||||
</content><category term="bar"></category><category term="oh"></category><category term="bar"></category><category term="yeah"></category></entry></feed>
|
||||
|
|
@ -1,12 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom"><title>A Pelican Blog - cat1</title><link href="/" rel="alternate"></link><link href="/feeds/cat1.atom.xml" rel="self"></link><id>/</id><updated>2011-04-20T00:00:00+00:00</updated><entry><title>A markdown powered article</title><link href="/a-markdown-powered-article.html" rel="alternate"></link><published>2011-04-20T00:00:00+00:00</published><updated>2011-04-20T00:00:00+00:00</updated><author><name></name></author><id>tag:None,2011-04-20:/a-markdown-powered-article.html</id><summary type="html"><p>You're mutually oblivious.</p>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom"><title>A Pelican Blog - cat1</title><link href="/" rel="alternate"></link><link href="/feeds/cat1.atom.xml" rel="self"></link><id>/</id><updated>2011-04-20T00:00:00+00:00</updated><entry><title>A markdown powered article</title><link href="/a-markdown-powered-article.html" rel="alternate"></link><published>2011-04-20T00:00:00+00:00</published><updated>2011-04-20T00:00:00+00:00</updated><author><name></name></author><id>tag:None,2011-04-20:/a-markdown-powered-article.html</id><content type="html"><p>You're mutually oblivious.</p>
|
||||
<p><a href="/unbelievable.html">a root-relative link to unbelievable</a>
|
||||
<a href="/unbelievable.html">a file-relative link to unbelievable</a></p></summary><content type="html"><p>You're mutually oblivious.</p>
|
||||
<p><a href="/unbelievable.html">a root-relative link to unbelievable</a>
|
||||
<a href="/unbelievable.html">a file-relative link to unbelievable</a></p></content></entry><entry><title>Article 1</title><link href="/article-1.html" rel="alternate"></link><published>2011-02-17T00:00:00+00:00</published><updated>2011-02-17T00:00:00+00:00</updated><author><name></name></author><id>tag:None,2011-02-17:/article-1.html</id><summary type="html"><p>Article 1</p>
|
||||
</summary><content type="html"><p>Article 1</p>
|
||||
</content></entry><entry><title>Article 2</title><link href="/article-2.html" rel="alternate"></link><published>2011-02-17T00:00:00+00:00</published><updated>2011-02-17T00:00:00+00:00</updated><author><name></name></author><id>tag:None,2011-02-17:/article-2.html</id><summary type="html"><p>Article 2</p>
|
||||
</summary><content type="html"><p>Article 2</p>
|
||||
</content></entry><entry><title>Article 3</title><link href="/article-3.html" rel="alternate"></link><published>2011-02-17T00:00:00+00:00</published><updated>2011-02-17T00:00:00+00:00</updated><author><name></name></author><id>tag:None,2011-02-17:/article-3.html</id><summary type="html"><p>Article 3</p>
|
||||
</summary><content type="html"><p>Article 3</p>
|
||||
</content></entry></feed>
|
||||
<a href="/unbelievable.html">a file-relative link to unbelievable</a></p></content><category term="cat1"></category></entry><entry><title>Article 1</title><link href="/article-1.html" rel="alternate"></link><published>2011-02-17T00:00:00+00:00</published><updated>2011-02-17T00:00:00+00:00</updated><author><name></name></author><id>tag:None,2011-02-17:/article-1.html</id><content type="html"><p>Article 1</p>
|
||||
</content><category term="cat1"></category></entry><entry><title>Article 2</title><link href="/article-2.html" rel="alternate"></link><published>2011-02-17T00:00:00+00:00</published><updated>2011-02-17T00:00:00+00:00</updated><author><name></name></author><id>tag:None,2011-02-17:/article-2.html</id><content type="html"><p>Article 2</p>
|
||||
</content><category term="cat1"></category></entry><entry><title>Article 3</title><link href="/article-3.html" rel="alternate"></link><published>2011-02-17T00:00:00+00:00</published><updated>2011-02-17T00:00:00+00:00</updated><author><name></name></author><id>tag:None,2011-02-17:/article-3.html</id><content type="html"><p>Article 3</p>
|
||||
</content><category term="cat1"></category></entry></feed>
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -5,12 +5,12 @@ as well as <strong>inline markup</strong>.</p>
|
|||
<div class="section" id="this-is-a-simple-title">
|
||||
<h2>This is a simple title</h2>
|
||||
<p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p>
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
|
||||
<pre class="literal-block">
|
||||
&gt;&gt;&gt; from ipdb import set_trace
|
||||
&gt;&gt;&gt; set_trace()
|
||||
</pre>
|
||||
<p>→ And now try with some utf8 hell: ééé</p>
|
||||
</div>
|
||||
</content><category term="foo"></category><category term="bar"></category><category term="foobar"></category></entry></feed>
|
||||
</content><category term="yeah"></category><category term="foo"></category><category term="bar"></category><category term="foobar"></category></entry></feed>
|
||||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>FILENAME_METADATA example</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -58,10 +55,10 @@
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>A Pelican Blog</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -57,7 +54,7 @@
|
|||
|
||||
<p>In <a href="/category/misc.html">misc</a>.</p>
|
||||
<p>tags: <a href="/tag/foo.html">foo</a> <a href="/tag/bar.html">bar</a> <a href="/tag/baz.html">baz</a> </p>Translations:
|
||||
<a href="/second-article-fr.html">fr</a>
|
||||
<a href="/second-article-fr.html" hreflang="fr">fr</a>
|
||||
|
||||
</footer><!-- /.post-info --> <p>This is some article, in english</p>
|
||||
|
||||
|
|
@ -190,12 +187,14 @@ as well as <strong>inline markup</strong>.</p>
|
|||
By <a class="url fn" href="/author/alexis-metaireau.html">Alexis Métaireau</a>
|
||||
</address>
|
||||
<p>In <a href="/category/bar.html">bar</a>.</p>
|
||||
<p>tags: <a href="/tag/oh.html">oh</a> <a href="/tag/bar.html">bar</a> <a href="/tag/yeah.html">yeah</a> </p>
|
||||
<p>tags: <a href="/tag/oh.html">oh</a> <a href="/tag/bar.html">bar</a> <a href="/tag/yeah.html">yeah</a> </p>Translations:
|
||||
<a href="/oh-yeah-fr.html" hreflang="fr">fr</a>
|
||||
|
||||
</footer><!-- /.post-info --> <div class="section" id="why-not">
|
||||
<h2>Why not ?</h2>
|
||||
<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
|
||||
YEAH !</p>
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
</div>
|
||||
|
||||
<a class="readmore" href="/oh-yeah.html">read more</a>
|
||||
|
|
@ -265,10 +264,10 @@ pelican.conf, it will …</p></div>
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
69
pelican/tests/output/basic/oh-yeah-fr.html
Normal file
69
pelican/tests/output/basic/oh-yeah-fr.html
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>Trop bien !</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
<link rel="alternate" hreflang="en" href="/oh-yeah.html">
|
||||
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
<li><a href="/pages/this-is-a-test-page.html">This is a test page</a></li>
|
||||
<li><a href="/category/bar.html">bar</a></li>
|
||||
<li><a href="/category/cat1.html">cat1</a></li>
|
||||
<li class="active"><a href="/category/misc.html">misc</a></li>
|
||||
<li><a href="/category/yeah.html">yeah</a></li>
|
||||
</ul></nav>
|
||||
</header><!-- /#banner -->
|
||||
<section id="content" class="body">
|
||||
<article>
|
||||
<header>
|
||||
<h1 class="entry-title">
|
||||
<a href="/oh-yeah-fr.html" rel="bookmark"
|
||||
title="Permalink to Trop bien !">Trop bien !</a></h1>
|
||||
</header>
|
||||
|
||||
<div class="entry-content">
|
||||
<footer class="post-info">
|
||||
<abbr class="published" title="2010-10-20T10:14:00+00:00">
|
||||
Published: Wed 20 October 2010
|
||||
</abbr>
|
||||
|
||||
<p>In <a href="/category/misc.html">misc</a>.</p>
|
||||
Translations:
|
||||
<a href="/oh-yeah.html" hreflang="en">en</a>
|
||||
|
||||
</footer><!-- /.post-info --> <p>Et voila du contenu en français</p>
|
||||
|
||||
</div><!-- /.entry-content -->
|
||||
|
||||
</article>
|
||||
</section>
|
||||
<section id="extras" class="body">
|
||||
<div class="social">
|
||||
<h2>social</h2>
|
||||
<ul>
|
||||
<li><a href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate">atom feed</a></li>
|
||||
|
||||
</ul>
|
||||
</div><!-- /.social -->
|
||||
</section><!-- /#extras -->
|
||||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -2,18 +2,17 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>Oh yeah !</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
<link rel="alternate" hreflang="fr" href="/oh-yeah-fr.html">
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -42,12 +41,14 @@
|
|||
By <a class="url fn" href="/author/alexis-metaireau.html">Alexis Métaireau</a>
|
||||
</address>
|
||||
<p>In <a href="/category/bar.html">bar</a>.</p>
|
||||
<p>tags: <a href="/tag/oh.html">oh</a> <a href="/tag/bar.html">bar</a> <a href="/tag/yeah.html">yeah</a> </p>
|
||||
<p>tags: <a href="/tag/oh.html">oh</a> <a href="/tag/bar.html">bar</a> <a href="/tag/yeah.html">yeah</a> </p>Translations:
|
||||
<a href="/oh-yeah-fr.html" hreflang="fr">fr</a>
|
||||
|
||||
</footer><!-- /.post-info --> <div class="section" id="why-not">
|
||||
<h2>Why not ?</h2>
|
||||
<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
|
||||
YEAH !</p>
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
</div>
|
||||
|
||||
</div><!-- /.entry-content -->
|
||||
|
|
@ -66,10 +67,10 @@ YEAH !</p>
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>Override url/save_as</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li class="active"><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -43,10 +40,10 @@ at a custom location.</p>
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>This is a test hidden page</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -43,10 +40,10 @@ Anyone can see this page but it's not linked to anywhere!</p>
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>This is a test page</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -28,7 +25,8 @@
|
|||
<h1 class="entry-title">This is a test page</h1>
|
||||
|
||||
<p>Just an image.</p>
|
||||
<img alt="alternate text" src="|filename|/pictures/Fat_Cat.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Fat_Cat.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="wrong path since 'images' folder does not exist" src="|filename|/images/Fat_Cat.jpg" style="width: 600px; height: 450px;" />
|
||||
|
||||
</section>
|
||||
<section id="extras" class="body">
|
||||
|
|
@ -43,10 +41,10 @@
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
BIN
pelican/tests/output/basic/pictures/Fat_Cat.jpg
Normal file
BIN
pelican/tests/output/basic/pictures/Fat_Cat.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
BIN
pelican/tests/output/basic/pictures/Sushi.jpg
Normal file
BIN
pelican/tests/output/basic/pictures/Sushi.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
BIN
pelican/tests/output/basic/pictures/Sushi_Macro.jpg
Normal file
BIN
pelican/tests/output/basic/pictures/Sushi_Macro.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
|
|
@ -1,19 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>Deuxième article</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
<link rel="alternate" hreflang="en" href="/second-article.html">
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -40,7 +39,7 @@
|
|||
|
||||
<p>In <a href="/category/misc.html">misc</a>.</p>
|
||||
<p>tags: <a href="/tag/foo.html">foo</a> <a href="/tag/bar.html">bar</a> <a href="/tag/baz.html">baz</a> </p>Translations:
|
||||
<a href="/second-article.html">en</a>
|
||||
<a href="/second-article.html" hreflang="en">en</a>
|
||||
|
||||
</footer><!-- /.post-info --> <p>Ceci est un article, en français.</p>
|
||||
|
||||
|
|
@ -60,10 +59,10 @@
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,17 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>Second article</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
<link rel="alternate" hreflang="fr" href="/second-article-fr.html">
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -40,7 +39,7 @@
|
|||
|
||||
<p>In <a href="/category/misc.html">misc</a>.</p>
|
||||
<p>tags: <a href="/tag/foo.html">foo</a> <a href="/tag/bar.html">bar</a> <a href="/tag/baz.html">baz</a> </p>Translations:
|
||||
<a href="/second-article-fr.html">fr</a>
|
||||
<a href="/second-article-fr.html" hreflang="fr">fr</a>
|
||||
|
||||
</footer><!-- /.post-info --> <p>This is some article, in english</p>
|
||||
|
||||
|
|
@ -60,10 +59,10 @@
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>A Pelican Blog - bar</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -35,7 +32,7 @@
|
|||
|
||||
<p>In <a href="/category/misc.html">misc</a>.</p>
|
||||
<p>tags: <a href="/tag/foo.html">foo</a> <a href="/tag/bar.html">bar</a> <a href="/tag/baz.html">baz</a> </p>Translations:
|
||||
<a href="/second-article-fr.html">fr</a>
|
||||
<a href="/second-article-fr.html" hreflang="fr">fr</a>
|
||||
|
||||
</footer><!-- /.post-info --><p>This is some article, in english</p>
|
||||
</article>
|
||||
|
|
@ -89,12 +86,14 @@ as well as <strong>inline markup</strong>.</p>
|
|||
By <a class="url fn" href="/author/alexis-metaireau.html">Alexis Métaireau</a>
|
||||
</address>
|
||||
<p>In <a href="/category/bar.html">bar</a>.</p>
|
||||
<p>tags: <a href="/tag/oh.html">oh</a> <a href="/tag/bar.html">bar</a> <a href="/tag/yeah.html">yeah</a> </p>
|
||||
<p>tags: <a href="/tag/oh.html">oh</a> <a href="/tag/bar.html">bar</a> <a href="/tag/yeah.html">yeah</a> </p>Translations:
|
||||
<a href="/oh-yeah-fr.html" hreflang="fr">fr</a>
|
||||
|
||||
</footer><!-- /.post-info --> <div class="section" id="why-not">
|
||||
<h2>Why not ?</h2>
|
||||
<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
|
||||
YEAH !</p>
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
</div>
|
||||
|
||||
<a class="readmore" href="/oh-yeah.html">read more</a>
|
||||
|
|
@ -114,10 +113,10 @@ YEAH !</p>
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>The baz tag</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -58,10 +55,10 @@
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>A Pelican Blog - foo</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -35,7 +32,7 @@
|
|||
|
||||
<p>In <a href="/category/misc.html">misc</a>.</p>
|
||||
<p>tags: <a href="/tag/foo.html">foo</a> <a href="/tag/bar.html">bar</a> <a href="/tag/baz.html">baz</a> </p>Translations:
|
||||
<a href="/second-article-fr.html">fr</a>
|
||||
<a href="/second-article-fr.html" hreflang="fr">fr</a>
|
||||
|
||||
</footer><!-- /.post-info --><p>This is some article, in english</p>
|
||||
</article>
|
||||
|
|
@ -86,10 +83,10 @@ as well as <strong>inline markup</strong>.</p>
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>A Pelican Blog - foobar</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -46,8 +43,8 @@
|
|||
<div class="section" id="this-is-a-simple-title">
|
||||
<h2>This is a simple title</h2>
|
||||
<p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p>
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" />
|
||||
<pre class="literal-block">
|
||||
>>> from ipdb import set_trace
|
||||
>>> set_trace()
|
||||
|
|
@ -68,10 +65,10 @@
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>Oh Oh Oh</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li class="active"><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -42,10 +39,10 @@
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>A Pelican Blog - yeah</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -37,12 +34,14 @@
|
|||
By <a class="url fn" href="/author/alexis-metaireau.html">Alexis Métaireau</a>
|
||||
</address>
|
||||
<p>In <a href="/category/bar.html">bar</a>.</p>
|
||||
<p>tags: <a href="/tag/oh.html">oh</a> <a href="/tag/bar.html">bar</a> <a href="/tag/yeah.html">yeah</a> </p>
|
||||
<p>tags: <a href="/tag/oh.html">oh</a> <a href="/tag/bar.html">bar</a> <a href="/tag/yeah.html">yeah</a> </p>Translations:
|
||||
<a href="/oh-yeah-fr.html" hreflang="fr">fr</a>
|
||||
|
||||
</footer><!-- /.post-info --><div class="section" id="why-not">
|
||||
<h2>Why not ?</h2>
|
||||
<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst !
|
||||
YEAH !</p>
|
||||
<img alt="alternate text" src="|filename|/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
<img alt="alternate text" src="/pictures/Sushi.jpg" style="width: 600px; height: 450px;" />
|
||||
</div>
|
||||
</article>
|
||||
</aside><!-- /#featured -->
|
||||
|
|
@ -58,10 +57,10 @@ YEAH !</p>
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="Pelican" />
|
||||
<title>A Pelican Blog - Tags</title>
|
||||
<link rel="stylesheet" href="/theme/css/main.css" />
|
||||
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="A Pelican Blog Atom Feed" />
|
||||
|
||||
<!--[if IE]>
|
||||
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body id="index" class="home">
|
||||
<header id="banner" class="body">
|
||||
<h1><a href="/">A Pelican Blog </a></h1>
|
||||
<h1><a href="/">A Pelican Blog</a></h1>
|
||||
<nav><ul>
|
||||
<li><a href="/tag/oh.html">Oh Oh Oh</a></li>
|
||||
<li><a href="/override/">Override url/save_as</a></li>
|
||||
|
|
@ -49,10 +46,10 @@
|
|||
|
||||
<footer id="contentinfo" class="body">
|
||||
<address id="about" class="vcard body">
|
||||
Proudly powered by <a href="http://getpelican.com/">Pelican</a>, which takes great advantage of <a href="http://python.org">Python</a>.
|
||||
Proudly powered by <a href="https://getpelican.com/">Pelican</a>, which takes great advantage of <a href="https://www.python.org/">Python</a>.
|
||||
</address><!-- /#about -->
|
||||
|
||||
<p>The theme is by <a href="http://coding.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
<p>The theme is by <a href="https://www.smashingmagazine.com/2009/08/designing-a-html-5-layout-from-scratch/">Smashing Magazine</a>, thanks!</p>
|
||||
</footer><!-- /#contentinfo -->
|
||||
|
||||
</body>
|
||||
|
|
|
|||
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