├── tests ├── __init__.py ├── content │ └── article_with_metadata.rst ├── test_readers.py ├── test_settings.py ├── default_conf.py └── test_contents.py ├── pelican ├── themes │ ├── simple │ │ └── templates │ │ │ ├── tag.html │ │ │ ├── tags.html │ │ │ ├── category.html │ │ │ ├── page.html │ │ │ ├── categories.html │ │ │ ├── author.html │ │ │ ├── archives.html │ │ │ ├── article.html │ │ │ ├── base.html │ │ │ └── index.html │ └── notmyidea │ │ ├── templates │ │ ├── authors.html │ │ ├── tags.html │ │ ├── tag.html │ │ ├── author.html │ │ ├── category.html │ │ ├── comments.html │ │ ├── categories.html │ │ ├── translations.html │ │ ├── taglist.html │ │ ├── twitter.html │ │ ├── archives.html │ │ ├── page.html │ │ ├── github.html │ │ ├── disqus_script.html │ │ ├── analytics.html │ │ ├── pagination.html │ │ ├── article_infos.html │ │ ├── piwik.html │ │ ├── article.html │ │ ├── index.html │ │ └── base.html │ │ └── static │ │ ├── images │ │ └── icons │ │ │ ├── rss.png │ │ │ ├── lastfm.png │ │ │ ├── twitter.png │ │ │ ├── delicious.png │ │ │ └── linkedin.png │ │ └── css │ │ ├── wide.css │ │ ├── reset.css │ │ ├── pygment.css │ │ └── main.css ├── rstdirectives.py ├── paginator.py ├── settings.py ├── log.py ├── contents.py ├── readers.py ├── __init__.py ├── utils.py └── writers.py ├── samples ├── content │ ├── unwanted_file │ ├── extra │ │ └── robots.txt │ ├── cat1 │ │ ├── article2.rst │ │ ├── article3.rst │ │ ├── article1.rst │ │ └── markdown-article.md │ ├── pictures │ │ ├── Sushi.jpg │ │ ├── Fat_Cat.jpg │ │ └── Sushi_Macro.jpg │ ├── another_super_article-fr.rst │ ├── unbelievable.rst │ ├── draft_article.rst │ ├── pages │ │ └── test_page.rst │ ├── another_super_article.rst │ └── super_article.rst └── pelican.conf.py ├── docs ├── _themes │ ├── .gitignore │ └── pelican │ │ ├── theme.conf │ │ ├── layout.html │ │ └── static │ │ └── pelican.css_t ├── _static │ ├── pelican.gif │ ├── pelican.png │ └── theme-basic.zip ├── fr │ ├── conventions.rst │ ├── astuces.rst │ ├── faq.rst │ ├── index.rst │ ├── installation.rst │ ├── bases.rst │ ├── parametres_article.rst │ ├── configuration.rst │ ├── pelican-themes.rst │ └── themes.rst ├── tips.rst ├── contribute.rst ├── faq.rst ├── conf.py ├── importer.rst ├── index.rst ├── internals.rst ├── Makefile ├── pelican-themes.rst ├── getting_started.rst └── themes.rst ├── bin └── pelican ├── MANIFEST.in ├── .hgignore ├── .gitignore ├── tox.ini ├── TODO ├── THANKS ├── setup.py ├── .hgtags ├── CHANGELOG ├── README.rst └── tools ├── pelican-themes ├── pelican-import └── pelican-quickstart /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pelican/themes/simple/templates/tag.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pelican/themes/simple/templates/tags.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/authors.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/tags.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/content/unwanted_file: -------------------------------------------------------------------------------- 1 | not to be parsed 2 | -------------------------------------------------------------------------------- /docs/_themes/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /bin/pelican: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from pelican import main 3 | main() 4 | -------------------------------------------------------------------------------- /samples/content/extra/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /static/pictures 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.rst 2 | recursive-include pelican *.html *.css *png 3 | include LICENSE 4 | -------------------------------------------------------------------------------- /docs/_static/pelican.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n1k0/pelican/master/docs/_static/pelican.gif -------------------------------------------------------------------------------- /docs/_static/pelican.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n1k0/pelican/master/docs/_static/pelican.png -------------------------------------------------------------------------------- /docs/_static/theme-basic.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n1k0/pelican/master/docs/_static/theme-basic.zip -------------------------------------------------------------------------------- /samples/content/cat1/article2.rst: -------------------------------------------------------------------------------- 1 | Article 2 2 | ######### 3 | 4 | :date: 2011-02-17 5 | 6 | Article 2 7 | -------------------------------------------------------------------------------- /samples/content/cat1/article3.rst: -------------------------------------------------------------------------------- 1 | Article 3 2 | ######### 3 | 4 | :date: 2011-02-17 5 | 6 | Article 3 7 | -------------------------------------------------------------------------------- /samples/content/pictures/Sushi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n1k0/pelican/master/samples/content/pictures/Sushi.jpg -------------------------------------------------------------------------------- /samples/content/pictures/Fat_Cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n1k0/pelican/master/samples/content/pictures/Fat_Cat.jpg -------------------------------------------------------------------------------- /samples/content/cat1/article1.rst: -------------------------------------------------------------------------------- 1 | Article 1 2 | ######### 3 | 4 | :date: 2011-02-17 5 | :yeah: oh yeah ! 6 | 7 | Article 1 8 | -------------------------------------------------------------------------------- /samples/content/pictures/Sushi_Macro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n1k0/pelican/master/samples/content/pictures/Sushi_Macro.jpg -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | output/* 3 | *.pyc 4 | MANIFEST 5 | build 6 | dist 7 | docs/_build 8 | Paste-* 9 | *.egg-info 10 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/tag.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% block title %}{{ SITENAME }} - {{ tag }}{% endblock %} 3 | -------------------------------------------------------------------------------- /samples/content/cat1/markdown-article.md: -------------------------------------------------------------------------------- 1 | Title: A markdown powered article 2 | Date: 2011-04-20 3 | 4 | You're mutually oblivious. 5 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/author.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% block title %}{{ SITENAME }} - {{ author }}{% endblock %} 3 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/category.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% block title %}{{ SITENAME }} - {{ category }}{% endblock %} 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | .*.swp 3 | .*.swo 4 | *.pyc 5 | docs/_build 6 | docs/fr/_build 7 | build 8 | dist 9 | output 10 | tags 11 | .tox 12 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/static/images/icons/rss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n1k0/pelican/master/pelican/themes/notmyidea/static/images/icons/rss.png -------------------------------------------------------------------------------- /samples/content/another_super_article-fr.rst: -------------------------------------------------------------------------------- 1 | Trop bien ! 2 | ########### 3 | 4 | :lang: fr 5 | :slug: oh-yeah 6 | 7 | Et voila du contenu en français 8 | -------------------------------------------------------------------------------- /samples/content/unbelievable.rst: -------------------------------------------------------------------------------- 1 | Unbelievable ! 2 | ############## 3 | 4 | :date: 2010-10-15 20:30 5 | 6 | Or completely awesome. Depends the needs. 7 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/static/images/icons/lastfm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n1k0/pelican/master/pelican/themes/notmyidea/static/images/icons/lastfm.png -------------------------------------------------------------------------------- /pelican/themes/notmyidea/static/images/icons/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n1k0/pelican/master/pelican/themes/notmyidea/static/images/icons/twitter.png -------------------------------------------------------------------------------- /pelican/themes/notmyidea/static/images/icons/delicious.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n1k0/pelican/master/pelican/themes/notmyidea/static/images/icons/delicious.png -------------------------------------------------------------------------------- /pelican/themes/notmyidea/static/images/icons/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n1k0/pelican/master/pelican/themes/notmyidea/static/images/icons/linkedin.png -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/comments.html: -------------------------------------------------------------------------------- 1 | {% if DISQUS_SITENAME %}

There are comments.

{% endif %} 2 | -------------------------------------------------------------------------------- /pelican/themes/simple/templates/category.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% block content_title %} 3 |

Articles in the {{ category }} category

4 | {% endblock %} 5 | 6 | -------------------------------------------------------------------------------- /samples/content/draft_article.rst: -------------------------------------------------------------------------------- 1 | A draft article 2 | ############### 3 | 4 | :status: draft 5 | 6 | This is a draft article, it should live under the /drafts/ folder and not be 7 | listed anywhere else. 8 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py25,py26,py27 3 | 4 | [testenv] 5 | commands=py.test 6 | deps = 7 | Jinja2 8 | Pygments 9 | docutils 10 | feedgenerator 11 | unittest2 12 | pytest 13 | -------------------------------------------------------------------------------- /pelican/themes/simple/templates/page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{{ page.title }}{%endblock%} 3 | {% block content %} 4 |

{{ page.title }}

5 | {{ page.content }} 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /docs/_themes/pelican/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = pelican.css 4 | nosidebar = true 5 | pygments_style = fruity 6 | 7 | [options] 8 | index_logo_height = 120px 9 | index_logo = 10 | github_fork = 11 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/categories.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /pelican/themes/simple/templates/categories.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /pelican/themes/simple/templates/author.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | 3 | {% block title %}{{ SITENAME }} - Articles by {{ author }}{% endblock %} 4 | {% block content_title %} 5 |

Articles by {{ author }}

6 | {% endblock %} 7 | 8 | -------------------------------------------------------------------------------- /samples/content/pages/test_page.rst: -------------------------------------------------------------------------------- 1 | This is a test page 2 | ################### 3 | 4 | :category: test 5 | 6 | Just an image. 7 | 8 | .. image:: pictures/Fat_Cat.jpg 9 | :height: 450 px 10 | :width: 600 px 11 | :alt: alternate text 12 | 13 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/translations.html: -------------------------------------------------------------------------------- 1 | {% if article.translations %} 2 | Translations: 3 | {% for translation in article.translations %} 4 | {{ translation.lang }} 5 | {% endfor %} 6 | {% endif %} 7 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/taglist.html: -------------------------------------------------------------------------------- 1 | {% if article.tags %}

tags: {% for tag in article.tags %}{{ tag }}{% endfor %}

{% endif %} 2 | {% if PDF_PROCESSOR %}

get the pdf

{% endif %} 3 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/twitter.html: -------------------------------------------------------------------------------- 1 | {% if TWITTER_USERNAME %} 2 | Tweet 3 | {% endif %} -------------------------------------------------------------------------------- /tests/content/article_with_metadata.rst: -------------------------------------------------------------------------------- 1 | 2 | This is a super article ! 3 | ######################### 4 | 5 | :tags: foo, bar, foobar 6 | :date: 2010-12-02 10:14 7 | :category: yeah 8 | :author: Alexis Métaireau 9 | :summary: 10 | Multi-line metadata should be supported 11 | as well as **inline markup**. 12 | -------------------------------------------------------------------------------- /pelican/themes/simple/templates/archives.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |

Archives for {{ SITENAME }}

4 | 5 |
6 | {% for article in dates %} 7 |
{{ article.locale_date }}
8 |
{{ article.title }}
9 | {% endfor %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/archives.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |

Archives for {{ SITENAME }}

5 | 6 |
7 | {% for article in dates %} 8 |
{{ article.locale_date }}
9 |
{{ article.title }}
10 | {% endfor %} 11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{{ page.title }}{% endblock %} 3 | {% block content %} 4 |
5 |

{{ page.title }}

6 | {% if PDF_PROCESSOR %}get 7 | the pdf{% endif %} 8 | {{ page.content }} 9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * Add a way to support pictures (see how sphinx makes that) 2 | * Make the program support UTF8-encoded files as input (and later: any encoding?) 3 | * Add status support (draft, published, hidden) 4 | * Add a serve + automatic generation behaviour. 5 | * Recompile only the changed files, not all. 6 | * Add a way to make the coffee (or not) 7 | * Add a sitemap generator. 8 | * read templates from the templates folder per default 9 | * add support of github via ghg import 10 | -------------------------------------------------------------------------------- /samples/content/another_super_article.rst: -------------------------------------------------------------------------------- 1 | Oh yeah ! 2 | ######### 3 | 4 | :tags: oh, bar, yeah 5 | :date: 2010-10-20 10:14 6 | :category: bar 7 | :author: Alexis Métaireau 8 | :slug: oh-yeah 9 | :license: WTFPL 10 | 11 | Why not ? 12 | ========= 13 | 14 | After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! 15 | YEAH ! 16 | 17 | .. image:: pictures/Sushi.jpg 18 | :height: 450 px 19 | :width: 600 px 20 | :alt: alternate text 21 | -------------------------------------------------------------------------------- /THANKS: -------------------------------------------------------------------------------- 1 | Some people have helped to improve pelican by contributing features, reporting 2 | bugs or giving ideas. Thanks to them ! 3 | 4 | - Dan Jacka 5 | - solsTiCe on linuxfr for reporting bugs 6 | - Guillaume B (Gui13) 7 | - Ronny Pfannschmidt 8 | - Jérome Renard 9 | - Nicolas Martin 10 | - David Kulak 11 | - Arnaud Bos 12 | - nblock (Florian) 13 | - Bruno Bord 14 | - Laureline Guérin 15 | - Samuel Martin 16 | - Marcus Fredriksson 17 | - Günter Kolousek 18 | - Simon Liedtke 19 | - Manuel F. Viera 20 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/github.html: -------------------------------------------------------------------------------- 1 | {% if GITHUB_URL %} 2 | 3 | {% if GITHUB_POSITION != "left" %} 4 | Fork me on GitHub 5 | {% else %} 6 | Fork me on GitHub 7 | {% endif %} 8 | 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/disqus_script.html: -------------------------------------------------------------------------------- 1 | {% if DISQUS_SITENAME %} 2 | 11 | {% endif %} 12 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/analytics.html: -------------------------------------------------------------------------------- 1 | {% if GOOGLE_ANALYTICS %} 2 | 6 | 11 | {% endif %} -------------------------------------------------------------------------------- /docs/fr/conventions.rst: -------------------------------------------------------------------------------- 1 | Conventions 2 | ########### 3 | 4 | Environnement de test 5 | ===================== 6 | 7 | Les exemples sont basées sur une distribution Debian. Pour les autres distributions, 8 | il y aura des ajustements à faire, notamment pour l’installation de Pelican. Les 9 | noms des paquets peuvent changer. 10 | 11 | Conventions typographiques 12 | ========================== 13 | 14 | Un petit rappel concernant les codes sources. 15 | 16 | * $ correspond à une ligne à exécuter en tant qu’utilisateur courant du systême ; 17 | * # correspond à une ligne à exécuter en tant que root ; 18 | * **settings.py** : Les noms des répertoires et fichiers sont en gras. 19 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/pagination.html: -------------------------------------------------------------------------------- 1 | {% if WITH_PAGINATION %} 2 |

3 | {% if articles_page.has_previous() %} 4 | {% if articles_page.previous_page_number() == 1 %} 5 | « 6 | {% else %} 7 | « 8 | {% endif %} 9 | {% endif %} 10 | Page {{ articles_page.number }} / {{ articles_paginator.num_pages }} 11 | {% if articles_page.has_next() %} 12 | » 13 | {% endif %} 14 |

15 | {% endif %} -------------------------------------------------------------------------------- /docs/fr/astuces.rst: -------------------------------------------------------------------------------- 1 | Trucs et astuces pour Pelican 2 | ############################# 3 | 4 | Personnaliser l'url d'un article pour Pelican 5 | ============================================= 6 | 7 | Par défaut, quand vous créez un article ayant pour titre *Mon article pour Pelican*, 8 | l'url par défaut devient *mon-article-pour-pelican.html*. Cependant, il est possible 9 | de modifier cela en utilisant la technique utilisée pour les traductions d'article, 10 | c'est à dire le paramètre *:slug:* :: 11 | 12 | Mon article pour Pelican 13 | ######################## 14 | 15 | :date: 2011-01-31 11:05 16 | :slug: super-article-pour-pelican 17 | 18 | bla, bla, bla … 19 | 20 | En prenant cet exemple ci dessus, votre url deviendra *super-article-pour-pelican.html* 21 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/article_infos.html: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /docs/_themes/pelican/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "basic/layout.html" %} 2 | {% block header %} 3 | {{ super() }} 4 | {% if pagename == 'index' %} 5 |
6 | {% endif %} 7 | {% endblock %} 8 | {% block footer %} 9 | {% if pagename == 'index' %} 10 |
11 | {% endif %} 12 | {% endblock %} 13 | {# do not display relbars #} 14 | {% block relbar1 %}{% endblock %} 15 | {% block relbar2 %} 16 | {% if theme_github_fork %} 17 | Fork me on GitHub 19 | {% endif %} 20 | {% endblock %} 21 | {% block sidebar1 %}{% endblock %} 22 | {% block sidebar2 %}{% endblock %} 23 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/static/css/wide.css: -------------------------------------------------------------------------------- 1 | @import url("main.css"); 2 | 3 | body { 4 | font:1.3em/1.3 "Hoefler Text","Georgia",Georgia,serif,sans-serif; 5 | } 6 | 7 | .body, #banner nav, #banner nav ul, #about, #featured, #content{ 8 | width: inherit; 9 | } 10 | 11 | #banner nav { 12 | -moz-border-radius: 0px; 13 | margin-bottom: 0px; 14 | } 15 | 16 | #banner nav ul{ 17 | padding-right: 50px; 18 | } 19 | 20 | #banner nav li{ 21 | float: right; 22 | } 23 | 24 | #banner nav li:first-child a { 25 | -moz-border-radius: 0px; 26 | } 27 | 28 | #banner h1 { 29 | margin-bottom: -18px; 30 | } 31 | 32 | #featured, #extras { 33 | padding: 50px; 34 | } 35 | 36 | #featured { 37 | padding-top: 20px; 38 | } 39 | 40 | #extras { 41 | padding-top: 0px; 42 | padding-bottom: 0px; 43 | } 44 | -------------------------------------------------------------------------------- /samples/content/super_article.rst: -------------------------------------------------------------------------------- 1 | This is a super article ! 2 | ######################### 3 | 4 | :tags: foo, bar, foobar 5 | :date: 2010-12-02 10:14 6 | :category: yeah 7 | :author: Alexis Métaireau 8 | :summary: 9 | Multi-line metadata should be supported 10 | as well as **inline markup**. 11 | 12 | Some content here ! 13 | 14 | This is a simple title 15 | ====================== 16 | 17 | And here comes the cool stuff_. 18 | 19 | .. image:: pictures/Sushi.jpg 20 | :height: 450 px 21 | :width: 600 px 22 | :alt: alternate text 23 | 24 | .. image:: pictures/Sushi_Macro.jpg 25 | :height: 450 px 26 | :width: 600 px 27 | :alt: alternate text 28 | 29 | :: 30 | 31 | >>> from ipdb import set_trace 32 | >>> set_trace() 33 | 34 | → And now try with some utf8 hell: ééé 35 | 36 | .. _stuff: http://books.couchdb.org/relax/design-documents/views 37 | -------------------------------------------------------------------------------- /pelican/themes/simple/templates/article.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |

{{ article.title }}

5 | 15 |
16 | {{ article.content }} 17 |
18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/piwik.html: -------------------------------------------------------------------------------- 1 | {% if PIWIK_URL and PIWIK_SITE_ID %} 2 | 16 | {% endif %} -------------------------------------------------------------------------------- /tests/test_readers.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import unittest2 3 | import os 4 | import datetime 5 | from pelican import readers 6 | 7 | CUR_DIR = os.path.dirname(__file__) 8 | CONTENT_PATH = os.path.join(CUR_DIR, 'content') 9 | 10 | def _filename(*args): 11 | return os.path.join(CONTENT_PATH, *args) 12 | 13 | 14 | class RstReaderTest(unittest2.TestCase): 15 | 16 | def test_article_with_metadata(self): 17 | reader = readers.RstReader() 18 | content, metadata = reader.read(_filename('article_with_metadata.rst')) 19 | expected = { 20 | 'category': 'yeah', 21 | 'author': u'Alexis Métaireau', 22 | 'title': 'This is a super article !', 23 | 'summary': 'Multi-line metadata should be supported\nas well as inline markup.', 24 | 'date': datetime.datetime(2010, 12, 2, 10, 14), 25 | 'tags': ['foo', 'bar', 'foobar'], 26 | } 27 | self.assertDictEqual(metadata, expected) 28 | -------------------------------------------------------------------------------- /docs/tips.rst: -------------------------------------------------------------------------------- 1 | Tips 2 | #### 3 | 4 | Here are some tips about pelican, which you might find useful. 5 | 6 | Publishing to github 7 | ==================== 8 | 9 | Github comes with an interesting "pages" feature: you can upload things there 10 | and it will be available directly from their servers. As pelican is a static 11 | file generator, we can take advantage of this. 12 | 13 | The excellent `ghp-import `_ makes this 14 | eally easy. You would have to install it:: 15 | 16 | $ pip install ghp-import 17 | 18 | Then, considering a repository containing your articles, you would simply have 19 | to run pelican and upload the output to github:: 20 | 21 | $ pelican -s pelican.conf.py . 22 | $ ghp-import output 23 | $ git push origin gh-pages 24 | 25 | And that's it. 26 | 27 | If you want you can put that directly into a post commit hook, so each time you 28 | commit, your blog is up to date on github! 29 | 30 | Put the following into `.git/hooks/post-commit`:: 31 | 32 | pelican -s pelican.conf.py . && ghp-import output && git push origin 33 | gh-pages 34 | -------------------------------------------------------------------------------- /docs/fr/faq.rst: -------------------------------------------------------------------------------- 1 | *Foire aux questions (FAQ)* 2 | 3 | Voici un résumé des questions fréquemment posées pour pelican. 4 | 5 | *Est-il obligatoire d'avoir un fichier de configuration ?* 6 | 7 | Non. Les fichiers de configuration sont juste un moyen facile de configurer 8 | pelican. Pour les opérations de base, il est possible de spécifier des 9 | options 10 | en invoquant pelican avec la ligne de commande (voir pelican --help pour 11 | plus 12 | d'informations à ce sujet) 13 | 14 | *Je crée mon propre thème, comment utiliser pygments?* 15 | 16 | Pygment ajoute quelques classes au contenu généré, de sorte qua colorisation 17 | de votre thème se fait grâce à un fichier css. Vous pouvez jeter un oeil à 18 | celui proposé par`sur le site du projet `_ 19 | 20 | *Comment puis-je créer mon propre thèm* 21 | 22 | Vueillez vous référer à :ref:`theming-pelican-fr`. 23 | 24 | *Comment puis-je aider?* 25 | 26 | Vous avez plusieurs options pour aider. Tout d'abord, vous pouvez utiliser 27 | le 28 | pélican, et signaler toute idée ou problème que vous avez sur le bugtracker 29 | . 30 | 31 | Si vous voulez contribuer, jeter un oeil au dépôt git , ajoutez vos 32 | modifications et faites une demande, je les regarderai dès que possible 33 | 34 | Vous pouvez aussi contribuer en créant des thèmes, et/ou compléter la 35 | documentation. 36 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/article.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{{ article.title }}{% endblock %} 3 | {% block content %} 4 |
5 |
6 |

{{ article.title 8 | }}

{% include 'twitter.html' %}
9 |
10 | {% include 'article_infos.html' %} 11 | {{ article.content }} 12 |
13 | {% if DISQUS_SITENAME %} 14 |
15 |

Comments !

16 |
17 | 25 |
26 | {% endif %} 27 | 28 |
29 |
30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/static/css/reset.css: -------------------------------------------------------------------------------- 1 | /* 2 | Name: Reset Stylesheet 3 | Description: Resets browser's default CSS 4 | Author: Eric Meyer 5 | Author URI: http://meyerweb.com/eric/tools/css/reset/ 6 | */ 7 | 8 | /* v1.0 | 20080212 */ 9 | html, body, div, span, applet, object, iframe, 10 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 11 | a, abbr, acronym, address, big, cite, code, 12 | del, dfn, em, font, img, ins, kbd, q, s, samp, 13 | small, strike, strong, sub, sup, tt, var, 14 | b, u, i, center, 15 | dl, dt, dd, ol, ul, li, 16 | fieldset, form, label, legend, 17 | table, caption, tbody, tfoot, thead, tr, th, td { 18 | background: transparent; 19 | border: 0; 20 | font-size: 100%; 21 | margin: 0; 22 | outline: 0; 23 | padding: 0; 24 | vertical-align: baseline; 25 | } 26 | 27 | body {line-height: 1;} 28 | 29 | ol, ul {list-style: none;} 30 | 31 | blockquote, q {quotes: none;} 32 | 33 | blockquote:before, blockquote:after, 34 | q:before, q:after { 35 | content: ''; 36 | content: none; 37 | } 38 | 39 | /* remember to define focus styles! */ 40 | :focus { 41 | outline: 0; 42 | } 43 | 44 | /* remember to highlight inserts somehow! */ 45 | ins {text-decoration: none;} 46 | del {text-decoration: line-through;} 47 | 48 | /* tables still need 'cellspacing="0"' in the markup */ 49 | table { 50 | border-collapse: collapse; 51 | border-spacing: 0; 52 | } -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup 3 | import sys 4 | 5 | VERSION = "2.7.2" # find a better way to do so. 6 | 7 | requires = ['feedgenerator', 'jinja2', 'pygments', 'docutils', 'pytz'] 8 | if sys.version_info < (2,7): 9 | requires.append('argparse') 10 | 11 | setup( 12 | name = "pelican", 13 | version = VERSION, 14 | url = 'http://alexis.notmyidea.org/pelican/', 15 | author = 'Alexis Metaireau', 16 | author_email = 'alexis@notmyidea.org', 17 | description = "A tool to generate a static blog, with restructured text (or markdown) input files.", 18 | long_description=open('README.rst').read(), 19 | packages = ['pelican'], 20 | include_package_data = True, 21 | install_requires = requires, 22 | scripts = ['bin/pelican', 'tools/pelican-themes', 'tools/pelican-import', 'tools/pelican-quickstart'], 23 | classifiers = ['Development Status :: 5 - Production/Stable', 24 | 'Environment :: Console', 25 | 'License :: OSI Approved :: GNU Affero General Public License v3', 26 | 'Operating System :: OS Independent', 27 | 'Programming Language :: Python', 28 | 'Topic :: Internet :: WWW/HTTP', 29 | 'Topic :: Software Development :: Libraries :: Python Modules', 30 | ], 31 | ) 32 | -------------------------------------------------------------------------------- /tests/test_settings.py: -------------------------------------------------------------------------------- 1 | import unittest2 2 | from os.path import dirname, abspath, join 3 | 4 | from pelican.settings import read_settings, _DEFAULT_CONFIG 5 | 6 | 7 | class TestSettingsFromFile(unittest2.TestCase): 8 | """Providing a file, it should read it, replace the default values and 9 | append new values to the settings, if any 10 | """ 11 | def setUp(self): 12 | self.PATH = abspath(dirname(__file__)) 13 | default_conf = join(self.PATH, 'default_conf.py') 14 | self.settings = read_settings(default_conf) 15 | 16 | def test_overwrite_existing_settings(self): 17 | self.assertEqual(self.settings.get('SITENAME'), u"Alexis' log") 18 | self.assertEqual(self.settings.get('SITEURL'), 19 | 'http://blog.notmyidea.org') 20 | 21 | def test_keep_default_settings(self): 22 | """keep default settings if not defined""" 23 | self.assertEqual(self.settings.get('DEFAULT_CATEGORY'), 24 | _DEFAULT_CONFIG['DEFAULT_CATEGORY']) 25 | 26 | def test_dont_copy_small_keys(self): 27 | """do not copy keys not in caps.""" 28 | self.assertNotIn('foobar', self.settings) 29 | 30 | def test_read_empty_settings(self): 31 | """providing no file should return the default values.""" 32 | settings = read_settings(None) 33 | self.assertDictEqual(settings, _DEFAULT_CONFIG) 34 | -------------------------------------------------------------------------------- /samples/pelican.conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | AUTHOR = u'Alexis Métaireau' 3 | SITENAME = u"Alexis' log" 4 | SITEURL = 'http://blog.notmyidea.org' 5 | 6 | GITHUB_URL = 'http://github.com/ametaireau/' 7 | DISQUS_SITENAME = "blog-notmyidea" 8 | PDF_GENERATOR = False 9 | REVERSE_CATEGORY_ORDER = True 10 | LOCALE = "" 11 | DEFAULT_PAGINATION = 2 12 | 13 | FEED_RSS = 'feeds/all.rss.xml' 14 | CATEGORY_FEED_RSS = 'feeds/%s.rss.xml' 15 | 16 | LINKS = (('Biologeek', 'http://biologeek.org'), 17 | ('Filyb', "http://filyb.info/"), 18 | ('Libert-fr', "http://www.libert-fr.com"), 19 | ('N1k0', "http://prendreuncafe.com/blog/"), 20 | (u'Tarek Ziadé', "http://ziade.org/blog"), 21 | ('Zubin Mithra', "http://zubin71.wordpress.com/"),) 22 | 23 | SOCIAL = (('twitter', 'http://twitter.com/ametaireau'), 24 | ('lastfm', 'http://lastfm.com/user/akounet'), 25 | ('github', 'http://github.com/ametaireau'),) 26 | 27 | # global metadata to all the contents 28 | DEFAULT_METADATA = (('yeah', 'it is'),) 29 | 30 | # static paths will be copied under the same name 31 | STATIC_PATHS = ["pictures",] 32 | 33 | # A list of files to copy from the source to the destination 34 | FILES_TO_COPY = (('extra/robots.txt', 'robots.txt'),) 35 | 36 | # foobar will not be used, because it's not in caps. All configuration keys 37 | # have to be in caps 38 | foobar = "barbaz" 39 | -------------------------------------------------------------------------------- /tests/default_conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | AUTHOR = u'Alexis Métaireau' 3 | SITENAME = u"Alexis' log" 4 | SITEURL = 'http://blog.notmyidea.org' 5 | 6 | GITHUB_URL = 'http://github.com/ametaireau/' 7 | DISQUS_SITENAME = "blog-notmyidea" 8 | PDF_GENERATOR = False 9 | REVERSE_CATEGORY_ORDER = True 10 | LOCALE = "" 11 | DEFAULT_PAGINATION = 2 12 | 13 | FEED_RSS = 'feeds/all.rss.xml' 14 | CATEGORY_FEED_RSS = 'feeds/%s.rss.xml' 15 | 16 | LINKS = (('Biologeek', 'http://biologeek.org'), 17 | ('Filyb', "http://filyb.info/"), 18 | ('Libert-fr', "http://www.libert-fr.com"), 19 | ('N1k0', "http://prendreuncafe.com/blog/"), 20 | (u'Tarek Ziadé', "http://ziade.org/blog"), 21 | ('Zubin Mithra', "http://zubin71.wordpress.com/"),) 22 | 23 | SOCIAL = (('twitter', 'http://twitter.com/ametaireau'), 24 | ('lastfm', 'http://lastfm.com/user/akounet'), 25 | ('github', 'http://github.com/ametaireau'),) 26 | 27 | # global metadata to all the contents 28 | DEFAULT_METADATA = (('yeah', 'it is'),) 29 | 30 | # static paths will be copied under the same name 31 | STATIC_PATHS = ["pictures",] 32 | 33 | # A list of files to copy from the source to the destination 34 | FILES_TO_COPY = (('extra/robots.txt', 'robots.txt'),) 35 | 36 | # foobar will not be used, because it's not in caps. All configuration keys 37 | # have to be in caps 38 | foobar = "barbaz" 39 | -------------------------------------------------------------------------------- /.hgtags: -------------------------------------------------------------------------------- 1 | 7acafbf7e47b1287525026ad8b4f1efe443d5403 1.2 2 | 7acafbf7e47b1287525026ad8b4f1efe443d5403 1.2 3 | ae850ab0fd62a98a98da7ce74ac794319c6a5066 1.2 4 | 54a0309f79d6c5b54d8e1e3b5e3f744856b68a73 1.1 5 | 8f5e0eb037768351eb08840e588a4364266a69b3 1.1.1 6 | bb986ed591734ca469f726753cbc48ebbfce0dcc 1.2.1 7 | 8a3dad99cbfa6bb5d0ef073213d0d86e0b4c5dba 1.2.2 8 | 4a20105a242ab154f6202aa6651979bfbb4cf95e 1.2.3 9 | 803aa0976cca3dd737777c640722988b1f3769fe 1.2.4 10 | 703c4511105fd9c8b85afda951a294c194e7cf3e 1.2.5 11 | 6e46a40aaa850a979f5d09dd95d02791ec7ab0ef 2.0 12 | bf14d1a5c1fae9475447698f0f9b8d35c551f732 2.1 13 | da86343ebd543e5865050e47ecb0937755528d13 2.1.1 14 | 760187f048bb23979402f950ecb5d3c5493995b1 2.2 15 | 20aa16fe4daa3b70f6c063f170edc916b49837ed 2.3 16 | f9c1d94081504f21f5b2ba147a38099e45db1769 2.4 17 | e65199a0b2706d2fb48f7a3c015e869716e0bec1 2.4.1 18 | 89dbd7b6f114508eae62fc821326f4797dfc8b23 2.4.2 19 | 979b4473af56a191a278c83058bc9c8fa1fde30e 2.4.3 20 | 26a444fbb78becae358afa0a5b47587db8739b21 2.4.4 21 | 3542b65fd1963ae7065b6a3bc912fbb6c150e98c 2.4.5 22 | 87745dfdd51b96bf18eaaf6c402effa902c1b856 2.5.0 23 | 294a2830a393d5a97671dc211dbdb5254a15e604 2.5.1 24 | 294a2830a393d5a97671dc211dbdb5254a15e604 2.5.1 25 | 92b31e41134cb2c1a156ce623338cf634d2ebc3e 2.5.1 26 | 7d728f8e771cbbc802ce81e424e08a8eecbd48dc 2.5.2 27 | 7d728f8e771cbbc802ce81e424e08a8eecbd48dc 2.5.2 28 | 6d368a1739a4ce48d2d04b00db04fa538e2bf90a 2.5.2 29 | 1f9dd44b546425216b1fa35fd88d3d532da8916b 2.5.3 30 | -------------------------------------------------------------------------------- /docs/contribute.rst: -------------------------------------------------------------------------------- 1 | How to contribute ? 2 | ################### 3 | There are many ways to contribute to pelican. You can enhance the 4 | documentation, add missing features, fix bugs or just report them. 5 | 6 | Don't hesitate to fork and make a pull request on github. 7 | 8 | Set up the development environment 9 | ================================== 10 | 11 | You're free to setup up the environment in any way you like. Here is a way 12 | using virtualenv and virtualenvwrapper. If you don't have them, you can install 13 | them using:: 14 | 15 | $ pip install virtualenvwrapper 16 | 17 | Virtual environments allow you to work on an installation of python which is 18 | not the one installed on your system. Especially, it will install the different 19 | projects under a different location. 20 | 21 | To create the virtualenv environment, you have to do:: 22 | 23 | $ mkvirtualenv pelican --no-site-package 24 | 25 | Then you would have to install all the dependencies:: 26 | 27 | $ pip install -r dev_requirements.txt 28 | 29 | Running the test suite 30 | ====================== 31 | 32 | Each time you add a feature, there are two things to do regarding tests: 33 | checking that the tests run in a right way, and be sure that you add tests for 34 | the feature you are working on or the bug you're fixing. 35 | 36 | The tests leaves under "pelican/tests" and you can run them using the 37 | "discover" feature of unittest2:: 38 | 39 | $ unit2 discover 40 | -------------------------------------------------------------------------------- /pelican/rstdirectives.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from docutils import nodes 3 | from docutils.parsers.rst import directives, Directive 4 | from pygments.formatters import HtmlFormatter 5 | from pygments import highlight 6 | from pygments.lexers import get_lexer_by_name, TextLexer 7 | 8 | INLINESTYLES = False 9 | DEFAULT = HtmlFormatter(noclasses=INLINESTYLES) 10 | VARIANTS = { 11 | 'linenos': HtmlFormatter(noclasses=INLINESTYLES, linenos=True), 12 | } 13 | 14 | 15 | class Pygments(Directive): 16 | """ Source code syntax hightlighting. 17 | """ 18 | required_arguments = 1 19 | optional_arguments = 0 20 | final_argument_whitespace = True 21 | option_spec = dict([(key, directives.flag) for key in VARIANTS]) 22 | has_content = True 23 | 24 | def run(self): 25 | self.assert_has_content() 26 | try: 27 | lexer = get_lexer_by_name(self.arguments[0]) 28 | except ValueError: 29 | # no lexer found - use the text one instead of an exception 30 | lexer = TextLexer() 31 | # take an arbitrary option if more than one is given 32 | formatter = self.options and VARIANTS[self.options.keys()[0]] \ 33 | or DEFAULT 34 | parsed = highlight(u'\n'.join(self.content), lexer, formatter) 35 | return [nodes.raw('', parsed, format='html')] 36 | 37 | directives.register_directive('code-block', Pygments) 38 | directives.register_directive('sourcecode', Pygments) 39 | -------------------------------------------------------------------------------- /docs/faq.rst: -------------------------------------------------------------------------------- 1 | Frequently Asked Questions (FAQ) 2 | ################################ 3 | 4 | Here is a summary of the frequently asked questions for pelican. 5 | 6 | Is it mandatory to have a configuration file ? 7 | ============================================== 8 | 9 | No, it's not. Configurations files are just an easy way to configure pelican. 10 | For the basic operations, it's possible to specify options while invoking 11 | pelican with the command line (see `pelican --help` for more informations about 12 | that) 13 | 14 | I'm creating my own theme, how to use pygments ? 15 | ================================================ 16 | 17 | Pygment add some classes to the generated content, so the theming of your theme 18 | will be done thanks to a css file. You can have a look to the one proposed by 19 | default `on the project website `_ 20 | 21 | How do I create my own theme ? 22 | ============================== 23 | 24 | Please refer yourself to :ref:`theming-pelican`. 25 | 26 | How can I help ? 27 | ================ 28 | 29 | You have different options to help. First, you can use pelican, and report any 30 | idea or problem you have on `the bugtracker 31 | `_. 32 | 33 | If you want to contribute, please have a look to `the git repository 34 | `_, fork it, add your changes and do 35 | a pull request, I'll review them as soon as possible. 36 | 37 | You can also contribute by creating themes, and making the documentation 38 | better. 39 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sys, os 3 | 4 | # -- General configuration ----------------------------------------------------- 5 | templates_path = ['_templates'] 6 | extensions = ['sphinx.ext.autodoc',] 7 | source_suffix = '.rst' 8 | master_doc = 'index' 9 | project = u'Pelican' 10 | copyright = u'2010, Alexis Metaireau and contributors' 11 | exclude_patterns = ['_build'] 12 | version = "2" 13 | release = version 14 | 15 | # -- Options for HTML output --------------------------------------------------- 16 | 17 | sys.path.append(os.path.abspath('_themes')) 18 | html_theme_path = ['_themes'] 19 | html_theme = 'pelican' 20 | 21 | html_theme_options = { 22 | 'nosidebar': True, 23 | 'index_logo': 'pelican.png', 24 | 'github_fork': 'ametaireau/pelican', 25 | } 26 | 27 | html_static_path = ['_static'] 28 | 29 | # Output file base name for HTML help builder. 30 | htmlhelp_basename = 'Pelicandoc' 31 | 32 | # -- Options for LaTeX output -------------------------------------------------- 33 | latex_documents = [ 34 | ('index', 'Pelican.tex', u'Pelican Documentation', 35 | u'Alexis Métaireau', 'manual'), 36 | ] 37 | 38 | # -- Options for manual page output -------------------------------------------- 39 | man_pages = [ 40 | ('index', 'pelican', u'pelican documentation', 41 | [u'Alexis Métaireau'], 1), 42 | ('pelican-themes', 'pelican-themes', u'A theme manager for Pelican', 43 | [u'Mickaël Raybaud'], 1), 44 | ('themes', 'pelican-theming', u'How to create themes for Pelican', 45 | [u'The Pelican contributors'], 1) 46 | ] 47 | -------------------------------------------------------------------------------- /pelican/themes/simple/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block head %} 5 | {% block title %}{{ SITENAME }}{% endblock title %} 6 | 7 | {% endblock head %} 8 | 9 | 10 | 11 | 14 | 28 | {% block content %} 29 | {% endblock %} 30 |
31 |
32 | Proudly powered by pelican, 33 | and obviously python! 34 |
35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /docs/importer.rst: -------------------------------------------------------------------------------- 1 | .. _import: 2 | 3 | ================================= 4 | Import from other blog software 5 | ================================= 6 | 7 | Description 8 | =========== 9 | 10 | ``pelican-import`` is a command line tool for converting articles from other 11 | software to ReStructuredText. The supported formats are: 12 | 13 | - Wordpress XML export 14 | - Dotclear export 15 | - RSS/ATOM feed 16 | 17 | The conversion from HTML to ReStructuredText relies on `pandoc 18 | `_. For Dotclear, if the source posts are 19 | written with Markdown syntax, they will not be converted (as Pelican also 20 | supports Markdown). 21 | 22 | Usage 23 | """"" 24 | 25 | | pelican-import [-h] [--wpfile] [--dotclear] [--feed] [-o OUTPUT] 26 | | [--dir-cat] 27 | | input 28 | 29 | Optional arguments: 30 | """"""""""""""""""" 31 | 32 | -h, --help show this help message and exit 33 | --wpfile Wordpress XML export 34 | --dotclear Dotclear export 35 | --feed Feed to parse 36 | -o OUTPUT, --output OUTPUT 37 | Output path 38 | --dir-cat Put files in directories with categories name 39 | 40 | Examples 41 | ======== 42 | 43 | for Wordpress:: 44 | 45 | $ pelican-import --wpfile -o ~/output ~/posts.xml 46 | 47 | for Dotclear:: 48 | 49 | $ pelican-import --dotclear -o ~/output ~/backup.txt 50 | 51 | Tests 52 | ===== 53 | 54 | To test the module, one can use sample files: 55 | 56 | - for Wordpress: http://wpcandy.com/made/the-sample-post-collection 57 | - for Dotclear: http://themes.dotaddict.org/files/public/downloads/lorem-backup.txt 58 | -------------------------------------------------------------------------------- /docs/fr/index.rst: -------------------------------------------------------------------------------- 1 | Pelican 2 | ####### 3 | 4 | Pelican est un generateur de blog simple codé en python 5 | 6 | * Écrivez vos articles directement dans votre éditeur favori (vim !) et 7 | directement en syntaxe reStructuredText ou Markdown ; 8 | * Un outil simple en ligne de conmmande pour (re)générer le blog ; 9 | * Sortie complètement statique, facile pour l'héberger n'importe où ; 10 | 11 | Fonctionnalités 12 | =============== 13 | 14 | Pelican supporte actuellement : 15 | 16 | * des articles de blog ; 17 | * des pages statiques ; 18 | * les commentaires via un service externe (`disqus `_) 19 | Notez qu'étant bien un service externe assez pratique, vous ne gérez pas 20 | vous même les commentaires. Ce qui pourrait occasionner une perte de vos données; 21 | * support de template (les templates sont crées avec `jinja2 `_) ; 22 | * génération optionnelle de vos pages et articles en pdf. 23 | 24 | Pourquoi le nom "Pelican" ? 25 | ============================ 26 | 27 | Vous n'avez pas remarqué ? "Pelican" est un anagramme pour "Calepin" ;) 28 | 29 | Code source 30 | =========== 31 | 32 | Vous pouvez accéder au code source via git à l'adresse 33 | http://github.com/ametaireau/pelican/ 34 | 35 | Feedback ! 36 | ========== 37 | 38 | Si vous voulez de nouvelles fonctionnalitées pour Pelican, n'hésitez pas à nous le dire, 39 | à cloner le dépôt, etc … C'est open source !!! 40 | 41 | Contactez Alexis à "alexis at notmyidea dot org" pour quelques requêtes ou retour d'expérience que ce soi ! 42 | 43 | Documentation 44 | ============= 45 | 46 | .. toctree:: 47 | :maxdepth: 2 48 | 49 | conventions 50 | installation 51 | bases 52 | configuration 53 | themes 54 | parametres_article 55 | astuces 56 | faq 57 | pelican-themes 58 | -------------------------------------------------------------------------------- /pelican/themes/simple/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 | {% block content_title %} 5 |

All articles

6 | {% endblock %} 7 | 8 |
    9 | {% for article in articles_page.object_list %} 10 |
  1. 18 | {% endfor %} 19 |
20 |

21 | {% if articles_page.has_previous() %} 22 | {% if articles_page.previous_page_number() == 1 %} 23 | « 24 | {% else %} 25 | « 26 | {% endif %} 27 | {% endif %} 28 | Page {{ articles_page.number }} / {{ articles_paginator.num_pages }} 29 | {% if articles_page.has_next() %} 30 | » 31 | {% endif %} 32 |

33 |
34 | {% endblock content %} 35 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 3.0 2 | 3 | * dotclear importer 4 | * Markdown extensions 5 | * Theme extensions 6 | * Plugins support 7 | 8 | 2.7 9 | 10 | * Uses logging rather than echoing to stdout 11 | * Support custom jinja filters 12 | * Compatibility with python 2.5 13 | * Add a theme manager 14 | * Packaged for debian 15 | * Add draft support 16 | 17 | 2.6 18 | 19 | * changes in the output directory structure 20 | * makes templates easier to work with / create 21 | * Add RSS support (was only atom previously) 22 | * Add tag support for the feeds 23 | * Enhance the documentation 24 | * Add another theme (brownstone) 25 | * Add translations 26 | * Add a way to use "cleaner urls" with a rewrite url module (or equivalent) 27 | * Add a tag cloud 28 | * Add an autoreloading feature: the blog is automatically regenerated each time a modification is detected 29 | * Translate the documentation in french 30 | * import a blog from an rss feed 31 | * Pagination support 32 | * Add skribit support 33 | 34 | 2.5 35 | 36 | * import from wordpress 37 | * add some new themes (martyalchin / wide-notmyidea) 38 | * first bug report ! 39 | * linkedin support 40 | * added a FAQ 41 | * google analytics support 42 | * twitter support 43 | * use relative urls not static ones 44 | 45 | 2.4 46 | 47 | * minor themes changes 48 | * add disqus support (so we have comments) 49 | * another code refactoring 50 | * add config settings about pages 51 | * blog entries can also be generated in pdf 52 | 53 | 2.3 54 | 55 | * markdown support 56 | 57 | 2.2 58 | 59 | * Prettify output 60 | * Manages static pages as well 61 | 62 | 2.1 63 | 64 | * Put the notmyidea theme by default 65 | 66 | 2.0 67 | 68 | * Refactoring to be more extensible 69 | * Change into the setting variables 70 | 71 | 1.2 72 | 73 | * Add a debug option 74 | * Add feeds per category 75 | * Use filsystem to get dates if no metadata provided 76 | * Add pygment support 77 | 78 | 1.1: 79 | 80 | * first working version 81 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Pelican 2 | ####### 3 | 4 | Pelican is a simple weblog generator, written in `Python `_. 5 | 6 | * Write your weblog entries directly with your editor of choice (vim!) and 7 | directly in `reStructuredText `_, or `Markdown `_. 8 | * A simple cli-tool to (re)generate the weblog. 9 | * Easy to interface with DVCSes and web hooks 10 | * Completely static output, so easy to host anywhere ! 11 | 12 | Features 13 | -------- 14 | 15 | Pelican currently supports: 16 | 17 | * blog articles and pages 18 | * comments, via an external service (disqus). Please notice that while 19 | it's useful, it's an external service, and you'll not manage the 20 | comments by yourself. It could potentially eat your data. 21 | * theming support (themes are done using `jinja2 `_) 22 | * PDF generation of the articles/pages (optional). 23 | * Translations 24 | * Syntactic recognition 25 | 26 | Have a look to `the documentation `_ for 27 | more informations. 28 | 29 | Why the name "Pelican" ? 30 | ------------------------ 31 | 32 | Heh, you didn't noticed? "Pelican" is an anagram for "Calepin" ;) 33 | 34 | Source code 35 | ----------- 36 | 37 | You can access the source code via git on http://github.com/ametaireau/pelican/ 38 | 39 | If you feel hackish, have a look to the `pelican's internals explanations 40 | `_. 41 | 42 | Feedback / Contact us 43 | ===================== 44 | 45 | If you want to see new features in Pelican, dont hesitate to tell me, to clone 46 | the repository, etc. That's open source, dude! 47 | 48 | Contact me at "alexis at notmyidea dot org" for any request/feedback! You can 49 | also join the team at `#pelican on irc.freenode.org 50 | `_ 51 | (or if you don't have any IRC client, using `the webchat 52 | `_) 53 | for quick feedback. 54 | -------------------------------------------------------------------------------- /docs/fr/installation.rst: -------------------------------------------------------------------------------- 1 | Installation et mise à jour de Pelican 2 | ###################################### 3 | 4 | Installation 5 | ============ 6 | 7 | Il y a deux façons d’installer Pelican sur son système. La première est via l’utilitaire 8 | pip, l’autre façon est de télécharger Pelican via Github. Ici nous allons voir les deux 9 | façons de procéder. 10 | 11 | Via pip 12 | ------- 13 | 14 | Pour installer Pelican via pip, vous aurez besoin du paquet python-pip. puis installez Pelican :: 15 | 16 | # apt-get install python-pip 17 | # pip install pelican 18 | 19 | 20 | Via Github 21 | ---------- 22 | 23 | Pour installer Pelican en reprenant le code via Github, nous aurons besoin du paquet 24 | git-core pour récupérez les sources de Pelican. Puis nous procédons à l’installation :: 25 | 26 | # apt-get install git-core 27 | $ git clone https://github.com/ametaireau/pelican.git 28 | $ cd pelican 29 | # python setup.py install 30 | 31 | Mises à jour 32 | ============ 33 | 34 | Via pip 35 | ------- 36 | 37 | Rien de bien compliqué pour mettre à jour via pip :: 38 | 39 | $ cd votreRepertoireSource 40 | $ pip install --upgrade pelican 41 | 42 | 43 | Via Github 44 | ---------- 45 | 46 | C'est un peu plus long avec Github par contre :: 47 | 48 | $ cd votreRepertoireSource 49 | $ git pull origin master 50 | $ cd pelican 51 | # python setup.py install 52 | 53 | Vous aurez un message d’erreur si le module setuptools de python n’est pas installé. 54 | La manipulation est la suivante :: 55 | 56 | # apt-get install python-setuptools 57 | 58 | Alors, quelle méthode choisir ? 59 | =============================== 60 | 61 | Vous avez le choix entre deux méthodes, mais aussi entre deux concepts. La méthode 62 | de Github est la version de développement, où les modifications arrivent assez 63 | fréquemment sans être testées à fond. La version de pip est une version arrêtée avec un 64 | numéro de version dans laquelle vous aurez moins de bug. N’oubliez cependant pas 65 | que le projet est très jeune et manque donc de maturité. Si vous aimez avoir les toutes 66 | dernières versions utilisez Github, sinon penchez vous sur pip. 67 | 68 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Pelican 2 | ####### 3 | 4 | Pelican is a simple weblog generator, written in python. 5 | 6 | * Write your weblog entries directly with your editor of choice (vim!) and 7 | directly in restructured text, or markdown. 8 | * A simple cli-tool to (re)generate the weblog. 9 | * Easy to interface with DVCSes and web hooks 10 | * Completely static output, so easy to host anywhere ! 11 | 12 | Features 13 | ======== 14 | 15 | Pelican currently supports: 16 | 17 | * blog articles and simple pages 18 | * comments, via an external service (disqus). Please notice that while 19 | it's useful, it's an external service, and you'll not manage the 20 | comments by yourself. It could potentially eat your data. (optional) 21 | * easy theming (themes are done using `jinja2 `_) 22 | * PDF generation of the articles/pages (optional). 23 | * publication of articles in various languages 24 | * RSS/Atom feeds 25 | * wordpress/dotclear or RSS imports 26 | * integration with various tools: twitter/google analytics (optional) 27 | 28 | Why the name "Pelican" ? 29 | ======================== 30 | 31 | Heh, you didn't noticed? "Pelican" is an anagram for "Calepin" ;) 32 | 33 | Source code 34 | =========== 35 | 36 | You can access the source code via git on http://github.com/ametaireau/pelican/ 37 | 38 | Feedback / Contact us 39 | ===================== 40 | 41 | If you want to see new features in Pelican, dont hesitate to tell me, to clone 42 | the repository, etc. That's open source, dude! 43 | 44 | Contact me at "alexis at notmyidea dot org" for any request/feedback! You can 45 | also join the team at `#pelican on irc.freenode.org 46 | `_ 47 | (or if you don't have any IRC client, using `the webchat 48 | `_) 49 | for quick feedback. 50 | 51 | Documentation 52 | ============= 53 | 54 | A french version of the documentation is available at :doc:`fr/index`. 55 | 56 | .. toctree:: 57 | :maxdepth: 2 58 | 59 | getting_started 60 | settings 61 | themes 62 | internals 63 | pelican-themes 64 | importer 65 | faq 66 | tips 67 | contribute 68 | -------------------------------------------------------------------------------- /docs/fr/bases.rst: -------------------------------------------------------------------------------- 1 | Les bases de Pelican 2 | #################### 3 | 4 | Créer son premier article 5 | ========================= 6 | 7 | Pour créer notre premier article, nous allons éditer un fichier, par exemple premier_article.rst :: 8 | 9 | Premier article pour Pelican 10 | ############################ 11 | :author: Guillaume 12 | :date: 2011-01-08 10:20 13 | :category: GNU-Linux 14 | :tags: tutoriel, git 15 | Ceci est un tutoriel pour configurer git. 16 | Bla, bla, bla .... 17 | 18 | Maintenant que ce fichier est créé, on va lancer la création du blog :: 19 | 20 | pelican . 21 | 22 | Vous aller obtenir une sortie comme celle ci — $PATH représente le dossier où vous 23 | avez créé votre article :: 24 | 25 | [ok] writing $PATH/output/feeds/all.atom.xml 26 | [ok] writing $PATH/output/feeds/GNU/Linux.atom.xml 27 | [ok] writing $PATH/output/feeds/all-en.atom.xml 28 | [ok] writing $PATH/output/premier-article-pour-pelican.html 29 | [ok] writing $PATH/output/index.html 30 | [ok] writing $PATH/output/tags.html 31 | [ok] writing $PATH/output/categories.html 32 | [ok] writing $PATH/output/archives.html 33 | [ok] writing $PATH/output/tag/tutoriel.html 34 | [ok] writing $PATH/output/tag/git.html 35 | [ok] writing $PATH/output/category/GNU-Linux.html 36 | 37 | 38 | Première analyse 39 | ================ 40 | 41 | Nous allons décortiquer un peu tout ça ensemble. 42 | 43 | * Un dossier output/ a été créé pour y mettre le fichiers xml et html du blog. 44 | * Dans le dossier feeds/, nous retrouvons les différents flux de syndication. 45 | * Le fichier de l’article et la page principale du blog a été généré. 46 | * Le répertoire tag/ propose une page par tag. 47 | * La page correspondant à la catégorie est générée dans le répertoire category/ 48 | 49 | Si vous ouvrez le fichier index.html — ou un autre — avec votre navigateur, vous 50 | remarquerez que : 51 | 52 | * Le thème utilisé par défaut est notmyidea 53 | * Le nom du blog est A Pelican Blog. 54 | 55 | Bien évidemment, il y a des paramètres de base que l’on peut modifier pour mettre 56 | un peu tout ça à sa sauce. C’est ce que nous allons voir au travers du fichier de configuration. 57 | 58 | 59 | -------------------------------------------------------------------------------- /tests/test_contents.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | from unittest2 import TestCase 3 | 4 | from pelican.contents import Page 5 | from pelican.settings import _DEFAULT_CONFIG 6 | 7 | class TestPage(TestCase): 8 | 9 | def test_use_args(self): 10 | # creating a page with arguments passed to the connstructor should use 11 | # them to initialise object's attributes 12 | metadata = {'foo': 'bar', 'foobar': 'baz'} 13 | page = Page('content', metadata=metadata) 14 | for key, value in metadata.items(): 15 | self.assertTrue(hasattr(page, key)) 16 | self.assertEqual(value, getattr(page, key)) 17 | self.assertEqual(page.content, "content") 18 | 19 | def test_mandatory_properties(self): 20 | # if the title is not set, must throw an exception 21 | page = Page('content') 22 | with self.assertRaises(NameError) as cm: 23 | page.check_properties() 24 | 25 | page = Page('content', metadata={'title': 'foobar'}) 26 | page.check_properties() 27 | 28 | def test_slug(self): 29 | # if a title is given, it should be used to generate the slug 30 | page = Page('content', {'title': 'foobar is foo'}) 31 | self.assertEqual(page.slug, 'foobar-is-foo') 32 | 33 | def test_defaultlang(self): 34 | # if no lang is given, default to the default one 35 | page = Page('content') 36 | self.assertEqual(page.lang, _DEFAULT_CONFIG['DEFAULT_LANG']) 37 | 38 | # it is possible to specify the lang in the metadata infos 39 | page = Page('content', {'lang': 'fr'}) 40 | self.assertEqual(page.lang, 'fr') 41 | 42 | def test_save_as(self): 43 | # if a lang is not the default lang, save_as should be set accordingly 44 | page = Page('content', {'title': 'foobar', 'lang': 'fr'}) #default lang is en 45 | self.assertEqual(page.save_as, "foobar-fr.html") 46 | 47 | # otherwise, if a title is defined, save_as should be set 48 | page = Page('content', {'title': 'foobar'}) 49 | page.save_as = 'foobar.html' 50 | 51 | # if no title is given, there is no save_as 52 | page = Page('content') 53 | self.assertFalse(hasattr(page, 'save_as')) 54 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content_title %}{% endblock %} 3 | {% block content %} 4 | {% if articles %} 5 | {% for article in articles_page.object_list %} 6 | 7 | {# First item #} 8 | {% if loop.first and not articles_page.has_previous() %} 9 | 18 | {% if loop.length > 1 %} 19 |
20 |

Other articles

21 |
22 |
    23 | {% endif %} 24 | {# other items #} 25 | {% else %} 26 | {% if loop.first and articles_page.has_previous %} 27 |
    28 |
      29 | {% endif %} 30 |
    1. 31 |
      32 |

      {{ article.title }}

      33 |
      34 | 35 |
      36 | {% include 'article_infos.html' %} 37 | {{ article.summary }} 38 | read more 39 | {% include 'comments.html' %} 40 |
      41 |
    2. 42 | {% endif %} 43 | {% if loop.last and (articles_page.has_previous() 44 | or not articles_page.has_previous() and loop.length > 1) %} 45 | {% include 'pagination.html' %} 46 | {% endif %} 47 | {% endfor %} 48 | {% if loop.length > 1 or articles_page.has_previous() %} 49 |
    50 |
    51 | {% endif %} 52 | {% else %} 53 |
    54 |

    Pages

    55 | {% for page in PAGES %} 56 |
  1. {{ page.title }}
  2. 57 | {% endfor %} 58 |
    59 | {% endif %} 60 | {% endblock content %} 61 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/static/css/pygment.css: -------------------------------------------------------------------------------- 1 | .hll { 2 | background-color:#FFFFCC; 3 | } 4 | .c { 5 | color:#408090; 6 | font-style:italic; 7 | } 8 | .err { 9 | border:1px solid #FF0000; 10 | } 11 | .k { 12 | color:#007020; 13 | font-weight:bold; 14 | } 15 | .o { 16 | color:#666666; 17 | } 18 | .cm { 19 | color:#408090; 20 | font-style:italic; 21 | } 22 | .cp { 23 | color:#007020; 24 | } 25 | .c1 { 26 | color:#408090; 27 | font-style:italic; 28 | } 29 | .cs { 30 | background-color:#FFF0F0; 31 | color:#408090; 32 | } 33 | .gd { 34 | color:#A00000; 35 | } 36 | .ge { 37 | font-style:italic; 38 | } 39 | .gr { 40 | color:#FF0000; 41 | } 42 | .gh { 43 | color:#000080; 44 | font-weight:bold; 45 | } 46 | .gi { 47 | color:#00A000; 48 | } 49 | .go { 50 | color:#303030; 51 | } 52 | .gp { 53 | color:#C65D09; 54 | font-weight:bold; 55 | } 56 | .gs { 57 | font-weight:bold; 58 | } 59 | .gu { 60 | color:#800080; 61 | font-weight:bold; 62 | } 63 | .gt { 64 | color:#0040D0; 65 | } 66 | .kc { 67 | color:#007020; 68 | font-weight:bold; 69 | } 70 | .kd { 71 | color:#007020; 72 | font-weight:bold; 73 | } 74 | .kn { 75 | color:#007020; 76 | font-weight:bold; 77 | } 78 | .kp { 79 | color:#007020; 80 | } 81 | .kr { 82 | color:#007020; 83 | font-weight:bold; 84 | } 85 | .kt { 86 | color:#902000; 87 | } 88 | .m { 89 | color:#208050; 90 | } 91 | .s { 92 | color:#4070A0; 93 | } 94 | .na { 95 | color:#4070A0; 96 | } 97 | .nb { 98 | color:#007020; 99 | } 100 | .nc { 101 | color:#0E84B5; 102 | font-weight:bold; 103 | } 104 | .no { 105 | color:#60ADD5; 106 | } 107 | .nd { 108 | color:#555555; 109 | font-weight:bold; 110 | } 111 | .ni { 112 | color:#D55537; 113 | font-weight:bold; 114 | } 115 | .ne { 116 | color:#007020; 117 | } 118 | .nf { 119 | color:#06287E; 120 | } 121 | .nl { 122 | color:#002070; 123 | font-weight:bold; 124 | } 125 | .nn { 126 | color:#0E84B5; 127 | font-weight:bold; 128 | } 129 | .nt { 130 | color:#062873; 131 | font-weight:bold; 132 | } 133 | .nv { 134 | color:#BB60D5; 135 | } 136 | .ow { 137 | color:#007020; 138 | font-weight:bold; 139 | } 140 | .w { 141 | color:#BBBBBB; 142 | } 143 | .mf { 144 | color:#208050; 145 | } 146 | .mh { 147 | color:#208050; 148 | } 149 | .mi { 150 | color:#208050; 151 | } 152 | .mo { 153 | color:#208050; 154 | } 155 | .sb { 156 | color:#4070A0; 157 | } 158 | .sc { 159 | color:#4070A0; 160 | } 161 | .sd { 162 | color:#4070A0; 163 | font-style:italic; 164 | } 165 | .s2 { 166 | color:#4070A0; 167 | } 168 | .se { 169 | color:#4070A0; 170 | font-weight:bold; 171 | } 172 | .sh { 173 | color:#4070A0; 174 | } 175 | .si { 176 | color:#70A0D0; 177 | font-style:italic; 178 | } 179 | .sx { 180 | color:#C65D09; 181 | } 182 | .sr { 183 | color:#235388; 184 | } 185 | .s1 { 186 | color:#4070A0; 187 | } 188 | .ss { 189 | color:#517918; 190 | } 191 | .bp { 192 | color:#007020; 193 | } 194 | .vc { 195 | color:#BB60D5; 196 | } 197 | .vg { 198 | color:#BB60D5; 199 | } 200 | .vi { 201 | color:#BB60D5; 202 | } 203 | .il { 204 | color:#208050; 205 | } 206 | -------------------------------------------------------------------------------- /pelican/paginator.py: -------------------------------------------------------------------------------- 1 | # From django.core.paginator 2 | from math import ceil 3 | 4 | class Paginator(object): 5 | def __init__(self, object_list, per_page, orphans=0): 6 | self.object_list = object_list 7 | self.per_page = per_page 8 | self.orphans = orphans 9 | self._num_pages = self._count = None 10 | 11 | def page(self, number): 12 | "Returns a Page object for the given 1-based page number." 13 | bottom = (number - 1) * self.per_page 14 | top = bottom + self.per_page 15 | if top + self.orphans >= self.count: 16 | top = self.count 17 | return Page(self.object_list[bottom:top], number, self) 18 | 19 | def _get_count(self): 20 | "Returns the total number of objects, across all pages." 21 | if self._count is None: 22 | self._count = len(self.object_list) 23 | return self._count 24 | count = property(_get_count) 25 | 26 | def _get_num_pages(self): 27 | "Returns the total number of pages." 28 | if self._num_pages is None: 29 | hits = max(1, self.count - self.orphans) 30 | self._num_pages = int(ceil(hits / (float(self.per_page) or 1))) 31 | return self._num_pages 32 | num_pages = property(_get_num_pages) 33 | 34 | def _get_page_range(self): 35 | """ 36 | Returns a 1-based range of pages for iterating through within 37 | a template for loop. 38 | """ 39 | return range(1, self.num_pages + 1) 40 | page_range = property(_get_page_range) 41 | 42 | class Page(object): 43 | def __init__(self, object_list, number, paginator): 44 | self.object_list = object_list 45 | self.number = number 46 | self.paginator = paginator 47 | 48 | def __repr__(self): 49 | return '' % (self.number, self.paginator.num_pages) 50 | 51 | def has_next(self): 52 | return self.number < self.paginator.num_pages 53 | 54 | def has_previous(self): 55 | return self.number > 1 56 | 57 | def has_other_pages(self): 58 | return self.has_previous() or self.has_next() 59 | 60 | def next_page_number(self): 61 | return self.number + 1 62 | 63 | def previous_page_number(self): 64 | return self.number - 1 65 | 66 | def start_index(self): 67 | """ 68 | Returns the 1-based index of the first object on this page, 69 | relative to total objects in the paginator. 70 | """ 71 | # Special case, return zero if no items. 72 | if self.paginator.count == 0: 73 | return 0 74 | return (self.paginator.per_page * (self.number - 1)) + 1 75 | 76 | def end_index(self): 77 | """ 78 | Returns the 1-based index of the last object on this page, 79 | relative to total objects found (hits). 80 | """ 81 | # Special case for the last page because there can be orphans. 82 | if self.number == self.paginator.num_pages: 83 | return self.paginator.count 84 | return self.number * self.paginator.per_page 85 | 86 | -------------------------------------------------------------------------------- /docs/fr/parametres_article.rst: -------------------------------------------------------------------------------- 1 | Les paramètres des articles dans Pelican 2 | ######################################## 3 | 4 | Les catégories 5 | ============== 6 | 7 | Nous avons vu que pour affecter un article à une catégorie, nous avions le paramètre *:category:*. 8 | Il y a cependant plus simple, affecter un répertoire à une catégorie. 9 | 10 | Dans le répertoire ou vous avez vos articles, créez le repertoire **GNU-Linux** et déplacez y le fichier 11 | **premier_article.rst**. Bien évidemment nous ne verront pas la différence, car jusqu'ici *GNU-Linux* 12 | est notre catégorie par défaut. 13 | 14 | Nous allons faire un autre exemple d'article avec la catégorie Pelican. Créez le répertoire **Pelican** 15 | et collez cette exemple d'article :: 16 | 17 | Préparation de la documentation 18 | ############################### 19 | 20 | :date: 2011-01-27 15:28 21 | :tags: documentation 22 | 23 | Il y a quand même pas mal de boulot pour faire une documentation ! 24 | 25 | Et lancez la compilation du blog. Vous voyez que la catégorie est affectée automatiquement. 26 | 27 | Les tags 28 | ======== 29 | 30 | Pour les tags, il n'y a rien de compliqué. il suffit de mettre le(s) tags séparés si besoin d'une virgule. :: 31 | 32 | Préparation de la documentation 33 | ############################### 34 | 35 | :date: 2011-01-27 15:28 36 | :tags: documentation, pelican 37 | 38 | Par contre, par soucis de clarté au niveau des url je vous conseille de mettre les expression de plusieurs 39 | mots séparées par des tirets :: 40 | 41 | :tags: mise-a-jour 42 | 43 | et non :: 44 | 45 | :tags: mise a jour 46 | 47 | 48 | Les auteurs 49 | =========== 50 | 51 | Par défaut, vous pouvez indiqué votre nom en tant qu'auteur dans le fichier de configuration. 52 | S'il y a plusieurs auteurs pour le site, vous pouvez le définir manuellement dans 53 | l'article avec la méta-donnée :: 54 | 55 | :author: Guillaume 56 | 57 | La date 58 | ======= 59 | 60 | La date se met au format anglophone : **YYYY-MM-DD hh:mm** :: 61 | 62 | :date: 2011-01-31 14:12 63 | 64 | 65 | Les traductions 66 | =============== 67 | 68 | Pelican permet de générer un blog multilingue assez facilement. Pour cela nous devons : 69 | 70 | * Définir la langue de base du blog ; 71 | * Donner une référence à l'article initial ; 72 | * Définir la langue du fichier traduit et y reporter la référence. 73 | 74 | Pour définir la langue de base nous allons modifier le fichier **settings.py** et y rajouter la ligne suivante :: 75 | 76 | DEFAULT_LANG = "fr" 77 | 78 | Puis ajouter la référence dans notre article d'origine qui deviendra :: 79 | 80 | Préparation de la documentation 81 | ############################### 82 | 83 | :date: 2011-01-27 15:28 84 | :tags: documentation 85 | :slug: preparation-de-la-documentation 86 | 87 | Il y a quand même pas mal de boulot pour faire une documentation ! 88 | 89 | Nous n'avons plus qu'à créer l'article en anglais :: 90 | 91 | Start of documentation 92 | ###################### 93 | 94 | :slug: preparation-de-la-documention 95 | :lang: en 96 | 97 | There are still a lot of work to documentation ! 98 | 99 | **Il est important de comprendre que la valeur de :slug: deviendra votre url. Ne mettez donc pas un diminutif pour 100 | identifier l'article** 101 | 102 | Rien de plus à savoir pour traduire efficacement des articles. 103 | 104 | 105 | Maintenant que vous avez toutes les clés en main pour créer un article, nous allons passer à la personnalisation 106 | du fichier de configuration. 107 | -------------------------------------------------------------------------------- /pelican/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import locale 4 | 5 | from pelican import log 6 | 7 | DEFAULT_THEME = os.sep.join([os.path.dirname(os.path.abspath(__file__)), 8 | "themes/notmyidea"]) 9 | _DEFAULT_CONFIG = {'PATH': None, 10 | 'THEME': DEFAULT_THEME, 11 | 'OUTPUT_PATH': 'output/', 12 | 'MARKUP': ('rst', 'md'), 13 | 'STATIC_PATHS': ['images',], 14 | 'THEME_STATIC_PATHS': ['static',], 15 | 'FEED': 'feeds/all.atom.xml', 16 | 'CATEGORY_FEED': 'feeds/%s.atom.xml', 17 | 'TRANSLATION_FEED': 'feeds/all-%s.atom.xml', 18 | 'FEED_MAX_ITEMS': '', 19 | 'SITENAME': 'A Pelican Blog', 20 | 'DISPLAY_PAGES_ON_MENU': True, 21 | 'PDF_GENERATOR': False, 22 | 'DEFAULT_CATEGORY': 'misc', 23 | 'FALLBACK_ON_FS_DATE': True, 24 | 'CSS_FILE': 'main.css', 25 | 'REVERSE_ARCHIVE_ORDER': False, 26 | 'REVERSE_CATEGORY_ORDER': False, 27 | 'DELETE_OUTPUT_DIRECTORY': False, 28 | 'CLEAN_URLS': False, # use /blah/ instead /blah.html in urls 29 | 'RELATIVE_URLS': True, 30 | 'DEFAULT_LANG': 'en', 31 | 'TAG_CLOUD_STEPS': 4, 32 | 'TAG_CLOUD_MAX_ITEMS': 100, 33 | 'DIRECT_TEMPLATES': ('index', 'tags', 'categories', 'archives'), 34 | 'PAGINATED_DIRECT_TEMPLATES': ('index', ), 35 | 'PELICAN_CLASS': 'pelican.Pelican', 36 | 'DEFAULT_DATE_FORMAT': '%a %d %B %Y', 37 | 'DATE_FORMATS': {}, 38 | 'JINJA_EXTENSIONS': [], 39 | 'LOCALE': '', # default to user locale 40 | 'WITH_PAGINATION': False, 41 | 'DEFAULT_PAGINATION': 5, 42 | 'DEFAULT_ORPHANS': 0, 43 | 'DEFAULT_METADATA': (), 44 | 'FILES_TO_COPY': (), 45 | 'DEFAULT_STATUS': 'published', 46 | 'ARTICLE_PERMALINK_STRUCTURE': '' 47 | } 48 | 49 | def read_settings(filename): 50 | """Load a Python file into a dictionary. 51 | """ 52 | context = _DEFAULT_CONFIG.copy() 53 | if filename: 54 | tempdict = {} 55 | execfile(filename, tempdict) 56 | for key in tempdict: 57 | if key.isupper(): 58 | context[key] = tempdict[key] 59 | 60 | # Make the paths relative to the settings file 61 | for path in ['PATH', 'OUTPUT_PATH']: 62 | if path in context: 63 | if context[path] is not None and not os.path.isabs(context[path]): 64 | # FIXME: 65 | context[path] = os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(filename), context[path]))) 66 | 67 | # if locales is not a list, make it one 68 | locales = context['LOCALE'] 69 | 70 | if isinstance(locales, basestring): 71 | locales = [locales] 72 | 73 | # try to set the different locales, fallback on the default. 74 | if not locales: 75 | locales = _DEFAULT_CONFIG['LOCALE'] 76 | 77 | for locale_ in locales: 78 | try: 79 | locale.setlocale(locale.LC_ALL, locale_) 80 | break # break if it is successfull 81 | except locale.Error: 82 | pass 83 | else: 84 | log.warn("LOCALE option doesn't contain a correct value") 85 | 86 | if not 'TIMEZONE' in context: 87 | log.warn("No timezone information specified in the settings. Assuming your "\ 88 | "timezone is UTC for feed generation. "\ 89 | "Check http://docs.notmyidea.org/alexis/pelican/settings.html#timezone "\ 90 | "for more information") 91 | 92 | # set the locale 93 | return context 94 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block title %}{{ SITENAME }}{%endblock%} 5 | 6 | 7 | 8 | {% if FEED_RSS %} 9 | 10 | {% endif %} 11 | 12 | 14 | 15 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | {% include 'github.html' %} 26 | 42 | {% block content %} 43 | {% endblock %} 44 |
    45 | {% if LINKS %} 46 |
    47 |

    blogroll

    48 |
      49 | {% for name, link in LINKS %} 50 |
    • {{ name }}
    • 51 | {% endfor %} 52 |
    53 |
    54 | {% endif %} 55 | {% if SOCIAL %} 56 | 69 | {% endif %} 70 |
    71 | 72 | 79 | 80 | {% include 'analytics.html' %} 81 | {% include 'piwik.html' %} 82 | {% include 'disqus_script.html' %} 83 | 84 | 85 | -------------------------------------------------------------------------------- /docs/internals.rst: -------------------------------------------------------------------------------- 1 | Pelican internals 2 | ################# 3 | 4 | This section describe how pelican is working internally. As you'll see, it's 5 | quite simple, but a bit of documentation doesn't hurt :) 6 | 7 | Overall structure 8 | ================= 9 | 10 | What `pelican` does, is taking a list of files, and processing them, to some 11 | sort of output. Usually, the files are restructured text and markdown files, 12 | and the output is a blog, but it can be anything you want. 13 | 14 | I've separated the logic in different classes and concepts: 15 | 16 | * `writers` are responsible of all the writing process of the 17 | files. It's writing .html files, RSS feeds and so on. Since those operations 18 | are commonly used, the object is created once, and then passed to the 19 | generators. 20 | 21 | * `readers` are used to read from various formats (Markdown, and Restructured 22 | Text for now, but the system is extensible). Given a file, they return 23 | metadata (author, tags, category etc) and content (HTML formated) 24 | 25 | * `generators` generate the different outputs. For instance, pelican comes with 26 | `ArticlesGenerator` and `PageGenerator`, into others. Given 27 | a configurations, they can do whatever they want. Most of the time it's 28 | generating files from inputs. 29 | 30 | * `pelican` also uses `templates`, so it's easy to write you own theme. The 31 | syntax is `jinja2`, and, trust me, really easy to learn, so don't hesitate 32 | a second. 33 | 34 | How to implement a new reader ? 35 | =============================== 36 | 37 | There is an awesome markup language you want to add to pelican ? 38 | Well, the only thing you have to do is to create a class that have a `read` 39 | method, that is returning an HTML content and some metadata. 40 | 41 | Take a look to the Markdown reader:: 42 | 43 | class MarkdownReader(Reader): 44 | enabled = bool(Markdown) 45 | 46 | def read(self, filename): 47 | """Parse content and metadata of markdown files""" 48 | text = open(filename) 49 | md = Markdown(extensions = ['meta', 'codehilite']) 50 | content = md.convert(text) 51 | 52 | metadata = {} 53 | for name, value in md.Meta.items(): 54 | if name in _METADATA_FIELDS: 55 | meta = _METADATA_FIELDS[name](value[0]) 56 | else: 57 | meta = value[0] 58 | metadata[name.lower()] = meta 59 | return content, metadata 60 | 61 | Simple isn't it ? 62 | 63 | If your new reader requires additional Python dependencies then you should wrap 64 | their `import` statements in `try...except`. Then inside the reader's class 65 | set the `enabled` class attribute to mark import success or failure. This makes 66 | it possible for users to continue using their favourite markup method without 67 | needing to install modules for all the additional formats they don't use. 68 | 69 | How to implement a new generator ? 70 | ================================== 71 | 72 | Generators have basically two important methods. You're not forced to create 73 | both, only the existing ones will be called. 74 | 75 | * `generate_context`, that is called in a first place, for all the generators. 76 | Do whatever you have to do, and update the global context if needed. This 77 | context is shared between all generators, and will be passed to the 78 | templates. For instance, the `PageGenerator` `generate_context` method find 79 | all the pages, transform them into objects, and populate the context with 80 | them. Be careful to *not* output anything using this context at this stage, 81 | as it is likely to change by the effect of others generators. 82 | 83 | * `generate_output` is then called. And guess what is it made for ? Oh, 84 | generating the output :) That's here that you may want to look at the context 85 | and call the methods of the `writer` object, that is passed at the first 86 | argument of this function. In the `PageGenerator` example, this method will 87 | look at all the pages recorded in the global context, and output a file on 88 | the disk (using the writer method `write_file`) for each page encountered. 89 | -------------------------------------------------------------------------------- /pelican/log.py: -------------------------------------------------------------------------------- 1 | from logging import CRITICAL, ERROR, WARN, INFO, DEBUG 2 | from logging import critical, error, info, warning, warn, debug 3 | from logging import Formatter, getLogger, StreamHandler 4 | import sys 5 | import os 6 | 7 | global ANSI 8 | ANSI = { 9 | 'gray' : lambda(text) : u'\033[1;30m' + unicode(text) + u'\033[1;m', 10 | 'red' : lambda(text) : u'\033[1;31m' + unicode(text) + u'\033[1;m', 11 | 'green' : lambda(text) : u'\033[1;32m' + unicode(text) + u'\033[1;m', 12 | 'yellow' : lambda(text) : u'\033[1;33m' + unicode(text) + u'\033[1;m', 13 | 'blue' : lambda(text) : u'\033[1;34m' + unicode(text) + u'\033[1;m', 14 | 'magenta' : lambda(text) : u'\033[1;35m' + unicode(text) + u'\033[1;m', 15 | 'cyan' : lambda(text) : u'\033[1;36m' + unicode(text) + u'\033[1;m', 16 | 'white' : lambda(text) : u'\033[1;37m' + unicode(text) + u'\033[1;m', 17 | 'bgred' : lambda(text) : u'\033[1;41m' + unicode(text) + u'\033[1;m', 18 | 'bggreen' : lambda(text) : u'\033[1;42m' + unicode(text) + u'\033[1;m', 19 | 'bgbrown' : lambda(text) : u'\033[1;43m' + unicode(text) + u'\033[1;m', 20 | 'bgblue' : lambda(text) : u'\033[1;44m' + unicode(text) + u'\033[1;m', 21 | 'bgmagenta' : lambda(text) : u'\033[1;45m' + unicode(text) + u'\033[1;m', 22 | 'bgcyan' : lambda(text) : u'\033[1;46m' + unicode(text) + u'\033[1;m', 23 | 'bggray' : lambda(text) : u'\033[1;47m' + unicode(text) + u'\033[1;m', 24 | 'bgyellow' : lambda(text) : u'\033[1;43m' + unicode(text) + u'\033[1;m', 25 | 'bggrey' : lambda(text) : u'\033[1;100m' + unicode(text) + u'\033[1;m' 26 | } 27 | 28 | 29 | class ANSIFormatter(Formatter): 30 | """ 31 | Convert a `logging.LogReport' object into colored text, using ANSI escape sequences. 32 | """ 33 | ## colors: 34 | 35 | def format(self, record): 36 | if record.levelname is 'INFO': 37 | return ANSI['cyan']('-> ') + unicode(record.msg) 38 | elif record.levelname is 'WARNING': 39 | return ANSI['yellow'](record.levelname) + ': ' + unicode(record.msg) 40 | elif record.levelname is 'ERROR': 41 | return ANSI['red'](record.levelname) + ': ' + unicode(record.msg) 42 | elif record.levelname is 'CRITICAL': 43 | return ANSI['bgred'](record.levelname) + ': ' + unicode(record.msg) 44 | elif record.levelname is 'DEBUG': 45 | return ANSI['bggrey'](record.levelname) + ': ' + unicode(record.msg) 46 | else: 47 | return ANSI['white'](record.levelname) + ': ' + unicode(record.msg) 48 | 49 | 50 | class TextFormatter(Formatter): 51 | """ 52 | Convert a `logging.LogReport' object into text. 53 | """ 54 | 55 | def format(self, record): 56 | if not record.levelname or record.levelname is 'INFO': 57 | return record.msg 58 | else: 59 | return record.levelname + ': ' + record.msg 60 | 61 | 62 | class DummyFormatter(object): 63 | """ 64 | A dummy class. 65 | Return an instance of the appropriate formatter (ANSIFormatter if sys.stdout.isatty() is True, else TextFormatter) 66 | """ 67 | 68 | def __new__(cls, *args, **kwargs): 69 | if os.isatty(sys.stdout.fileno()): # thanks to http://stackoverflow.com/questions/2086961/how-can-i-determine-if-a-python-script-is-executed-from-crontab/2087031#2087031 70 | return ANSIFormatter(*args, **kwargs) 71 | else: 72 | return TextFormatter( *args, **kwargs) 73 | 74 | 75 | 76 | 77 | 78 | def init(level=None, logger=getLogger(), handler=StreamHandler()): 79 | fmt = DummyFormatter() 80 | handler.setFormatter(fmt) 81 | logger.addHandler(handler) 82 | if level: 83 | logger.setLevel(level) 84 | 85 | 86 | if __name__ == '__main__': 87 | init(level=DEBUG) 88 | debug('debug') 89 | info('info') 90 | warning('warning') 91 | error('error') 92 | critical('critical') 93 | 94 | 95 | __all__ = [ 96 | "debug", 97 | "info", 98 | "warn", 99 | "warning", 100 | "error", 101 | "critical", 102 | "DEBUG", 103 | "INFO", 104 | "WARN", 105 | "ERROR", 106 | "CRITICAL" 107 | ] 108 | -------------------------------------------------------------------------------- /docs/fr/configuration.rst: -------------------------------------------------------------------------------- 1 | Fichier de configuration 2 | ************************ 3 | 4 | On va créer un fichier de configuration que l’on va appeler **settings.py**. On peut 5 | utiliser Pelican sans faire ce fichier, mais il faudrait à chaque fois passer les paramètres 6 | en ligne de commande. Et comme il va nous servir à faire d’autres choses bien utile, 7 | autant l’appréhender de suite. Cependant, nous n’allons voir que la base pour l’instant. 8 | 9 | Paramètres de base 10 | ================== 11 | 12 | AUTHOR : 13 | Désigne l’auteur par défaut ; 14 | 15 | DEFAULT_CATEGORY : 16 | La catégorie par défaut des articles. Si ce paramètre n’est 17 | pas documenté, il prendra la valeur misc — pour miscellaneous (divers en français) ; 18 | 19 | SITENAME : 20 | Le nom de votre site ; 21 | 22 | OUTPUT_PATH : 23 | Le répertoire de sortie du blog. 24 | 25 | Quand je dis qu’on va faire simple, on fait simple ! 26 | Passons donc à ce quoi doit ressembler le fichier de configuration :: 27 | 28 | # -*- coding: utf-8 -*- 29 | AUTHOR = "Guillaume" 30 | DEFAULT_CATEGORY = "GNU-Linux" 31 | SITENAME = "Free Culture" 32 | 33 | 34 | Si vous avez un serveur comme Apache de configuré pour votre machine, vous 35 | pouvez paramétrer le répertoire de sortie vers **/var/www/blog** par exemple :: 36 | 37 | OUTPUT_PATH = "/var/www/blog" 38 | 39 | Une remarque importante. Si vous avez besoin de passer un caractère accentué, il 40 | faut le préciser que la chaine est en unicode en faisant par exemple 41 | *AUTHOR = u"Guillaume LAMÉ"* 42 | 43 | Pour bien vérifier que les paramètres sont bien pris en compte, nous allons enlever les lignes *:author: Guillaume* et *:category: GNU-Linux* de notre fichier 44 | **premier_article.rst** et regénérer le blog. 45 | 46 | Rafraichissez votre page, ce devrait être bon. 47 | 48 | Nous allons maintenant passer en revue les différents paramètres de Pelican. Je les 49 | ai regroupé par thème. Cependant, c’est surtout un listing avant de rentrer dans les 50 | détails au prochain chapitre. 51 | 52 | Flux de syndication 53 | =================== 54 | 55 | CATEGORY_FEED : 56 | Chemin d’écriture des flux Atom liés aux catégories ; 57 | 58 | CATEGORY_FEED_RSS : 59 | Idem pour les flux rss (Optionnel); 60 | 61 | FEED : 62 | Chemin du flux Atom global ; 63 | 64 | FEED_RSS : 65 | Chemin du flux Rss global (Optionnel); 66 | 67 | TAG_FEED : 68 | Chemin des flux Atom pour les tags (Optionnel); 69 | 70 | TAG_FEED_RSS : 71 | Chemin des flux Rss pour les tags (Optionnel). 72 | 73 | 74 | Traductions 75 | =========== 76 | 77 | DEFAULT_LANG : 78 | Le langage par défaut à utiliser. «*en*» par défaut ; 79 | 80 | TRANSLATION_FEED : 81 | Chemin du flux pour les traductions. 82 | 83 | 84 | Thèmes 85 | ====== 86 | 87 | CSS_FILE : 88 | Fichier css à utiliser si celui-ci est différent du fichier par défaut (*main.css*) ; 89 | 90 | DISPLAY_PAGES_ON_MENU : 91 | Affiche ou non les pages statiques sur le menu du thème ; 92 | 93 | DISQUS_SITENAME : 94 | Indiquer le nom du site spécifié sur Disqus ; 95 | 96 | GITHUB_URL : 97 | Indiquez votre url Github ; 98 | 99 | GOOGLE_ANALYTICS : 100 | 'UA-XXXX-YYYY' pour activer Google analytics ; 101 | 102 | JINJA_EXTENSIONS : 103 | Liste d'extension Jinja2 que vous souhaitez utiliser ; 104 | 105 | LINKS : 106 | Une liste de tuples (Titre, url) pour afficher la liste de lien ; 107 | 108 | PDF_PROCESSOR : 109 | Génère ou non les articles et pages au format pdf ; 110 | 111 | REVERSE_ARCHIVE_ORDER : 112 | Met les articles plus récent en tête de l'archive ; 113 | 114 | SOCIAL : 115 | Une liste de tuples (Titre, url) pour afficher la liste de lien dans la section "Social" ; 116 | 117 | STATIC_THEME_PATHS : 118 | Répertoire du thème que vous souhaitez importer dans l'arborescence finale ; 119 | 120 | THEME : 121 | Thème à utiliser: 122 | 123 | TWITTER_USERNAME : 124 | Permet d'afficher un bouton permettant le tweet des articles. 125 | 126 | Pelican est fournit avec :doc:`pelican-themes`, un script permettant de gérer les thèmes 127 | 128 | 129 | 130 | Paramètres divers 131 | ================= 132 | 133 | FALLBACK_ON_FS_DATE : 134 | Si *True*, Pelican se basera sur le *mtime* du fichier s'il n'y a pas de date spécifiée dans le fichier de l'article ; 135 | 136 | KEEP_OUTPUT DIRECTORY : 137 | Ne génère que les fichiers modifiés et n'efface pas le repertoire de sortie ; 138 | 139 | MARKUP : 140 | Langage de balisage à utiliser ; 141 | 142 | PATH : 143 | Répertoire à suivre pour les fichiers inclus ; 144 | 145 | SITEURL : 146 | URL de base de votre site ; 147 | 148 | STATIC_PATHS : 149 | Les chemins statiques que vous voulez avoir accès sur le chemin de sortie "statique" ; 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /pelican/contents.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from pelican.utils import slugify, truncate_html_words 3 | from pelican.log import * 4 | from pelican.settings import _DEFAULT_CONFIG 5 | from os import getenv 6 | from sys import platform, stdin 7 | 8 | class Page(object): 9 | """Represents a page 10 | Given a content, and metadata, create an adequate object. 11 | 12 | :param content: the string to parse, containing the original content. 13 | """ 14 | mandatory_properties = ('title',) 15 | 16 | def __init__(self, content, metadata=None, settings=None, filename=None): 17 | # init parameters 18 | if not metadata: 19 | metadata = {} 20 | if not settings: 21 | settings = _DEFAULT_CONFIG 22 | 23 | self._content = content 24 | self.translations = [] 25 | 26 | self.status = "published" # default value 27 | 28 | local_metadata = dict(settings.get('DEFAULT_METADATA', ())) 29 | local_metadata.update(metadata) 30 | 31 | # set metadata as attributes 32 | for key, value in local_metadata.items(): 33 | setattr(self, key.lower(), value) 34 | 35 | # default author to the one in settings if not defined 36 | if not hasattr(self, 'author'): 37 | if 'AUTHOR' in settings: 38 | self.author = settings['AUTHOR'] 39 | else: 40 | self.author = getenv('USER', 'John Doe') 41 | warning("Author of `{0}' unknow, assuming that his name is `{1}'".format(filename or self.title, self.author).decode("utf-8")) 42 | 43 | # manage languages 44 | self.in_default_lang = True 45 | if 'DEFAULT_LANG' in settings: 46 | default_lang = settings['DEFAULT_LANG'].lower() 47 | if not hasattr(self, 'lang'): 48 | self.lang = default_lang 49 | 50 | self.in_default_lang = (self.lang == default_lang) 51 | 52 | # create the slug if not existing, fro mthe title 53 | if not hasattr(self, 'slug') and hasattr(self, 'title'): 54 | self.slug = slugify(self.title) 55 | 56 | # create save_as from the slug (+lang) 57 | if not hasattr(self, 'save_as') and hasattr(self, 'slug'): 58 | if self.in_default_lang: 59 | self.save_as = '%s.html' % self.slug 60 | clean_url = '%s/' % self.slug 61 | else: 62 | self.save_as = '%s-%s.html' % (self.slug, self.lang) 63 | clean_url = '%s-%s/' % (self.slug, self.lang) 64 | 65 | # change the save_as regarding the settings 66 | if settings.get('CLEAN_URLS', False): 67 | self.url = clean_url 68 | elif hasattr(self, 'save_as'): 69 | self.url = self.save_as 70 | 71 | if filename: 72 | self.filename = filename 73 | 74 | # manage the date format 75 | if not hasattr(self, 'date_format'): 76 | if hasattr(self, 'lang') and self.lang in settings['DATE_FORMATS']: 77 | self.date_format = settings['DATE_FORMATS'][self.lang] 78 | else: 79 | self.date_format = settings['DEFAULT_DATE_FORMAT'] 80 | 81 | if hasattr(self, 'date'): 82 | if platform == 'win32': 83 | self.locale_date = self.date.strftime(self.date_format.encode('ascii','xmlcharrefreplace')).decode(stdin.encoding) 84 | else: 85 | self.locale_date = self.date.strftime(self.date_format.encode('ascii','xmlcharrefreplace')).decode('utf') 86 | 87 | # manage summary 88 | if not hasattr(self, 'summary'): 89 | self.summary = property(lambda self: truncate_html_words(self.content, 50)).__get__(self, Page) 90 | 91 | # manage status 92 | if not hasattr(self, 'status'): 93 | self.status = settings['DEFAULT_STATUS'] 94 | 95 | def check_properties(self): 96 | """test that each mandatory property is set.""" 97 | for prop in self.mandatory_properties: 98 | if not hasattr(self, prop): 99 | raise NameError(prop) 100 | 101 | @property 102 | def content(self): 103 | if hasattr(self, "_get_content"): 104 | content = self._get_content() 105 | else: 106 | content = self._content 107 | return content 108 | 109 | 110 | class Article(Page): 111 | mandatory_properties = ('title', 'date', 'category') 112 | 113 | 114 | class Quote(Page): 115 | base_properties = ('author', 'date') 116 | 117 | 118 | def is_valid_content(content, f): 119 | try: 120 | content.check_properties() 121 | return True 122 | except NameError, e: 123 | error(u"Skipping %s: impossible to find informations about '%s'" % (f, e)) 124 | return False 125 | -------------------------------------------------------------------------------- /pelican/readers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | try: 3 | import docutils 4 | import docutils.core 5 | import docutils.io 6 | from docutils.writers.html4css1 import HTMLTranslator 7 | 8 | # import the directives to have pygments support 9 | from pelican import rstdirectives 10 | except ImportError: 11 | core = False 12 | try: 13 | from markdown import Markdown 14 | except ImportError: 15 | Markdown = False 16 | import re 17 | 18 | from pelican.utils import get_date, open 19 | 20 | 21 | _METADATA_PROCESSORS = { 22 | 'tags': lambda x: map(unicode.strip, x.split(',')), 23 | 'date': lambda x: get_date(x), 24 | 'status': unicode.strip, 25 | } 26 | 27 | def _process_metadata(name, value): 28 | if name.lower() in _METADATA_PROCESSORS: 29 | return _METADATA_PROCESSORS[name.lower()](value) 30 | return value 31 | 32 | 33 | class Reader(object): 34 | enabled = True 35 | extensions = None 36 | 37 | class _FieldBodyTranslator(HTMLTranslator): 38 | 39 | def astext(self): 40 | return ''.join(self.body) 41 | 42 | def visit_field_body(self, node): 43 | pass 44 | 45 | def depart_field_body(self, node): 46 | pass 47 | 48 | 49 | def render_node_to_html(document, node): 50 | visitor = _FieldBodyTranslator(document) 51 | node.walkabout(visitor) 52 | return visitor.astext() 53 | 54 | def get_metadata(document): 55 | """Return the dict containing document metadata""" 56 | output = {} 57 | for docinfo in document.traverse(docutils.nodes.docinfo): 58 | for element in docinfo.children: 59 | if element.tagname == 'field': # custom fields (e.g. summary) 60 | name_elem, body_elem = element.children 61 | name = name_elem.astext() 62 | value = render_node_to_html(document, body_elem) 63 | else: # standard fields (e.g. address) 64 | name = element.tagname 65 | value = element.astext() 66 | 67 | output[name] = _process_metadata(name, value) 68 | return output 69 | 70 | 71 | class RstReader(Reader): 72 | enabled = bool(docutils) 73 | extension = "rst" 74 | 75 | def _parse_metadata(self, document): 76 | return get_metadata(document) 77 | 78 | def _get_publisher(self, filename): 79 | extra_params = {'initial_header_level': '2'} 80 | pub = docutils.core.Publisher(destination_class=docutils.io.StringOutput) 81 | pub.set_components('standalone', 'restructuredtext', 'html') 82 | pub.process_programmatic_settings(None, extra_params, None) 83 | pub.set_source(source_path=filename) 84 | pub.publish() 85 | return pub 86 | 87 | def read(self, filename): 88 | """Parses restructured text""" 89 | pub = self._get_publisher(filename) 90 | parts = pub.writer.parts 91 | content = parts.get('body') 92 | 93 | metadata = self._parse_metadata(pub.document) 94 | metadata.setdefault('title', parts.get('title')) 95 | 96 | return content, metadata 97 | 98 | 99 | class MarkdownReader(Reader): 100 | enabled = bool(Markdown) 101 | extension = "md" 102 | extensions = ['codehilite', 'extra'] 103 | 104 | def read(self, filename): 105 | """Parse content and metadata of markdown files""" 106 | text = open(filename) 107 | md = Markdown(extensions=set(self.extensions + ['meta'])) 108 | content = md.convert(text) 109 | 110 | metadata = {} 111 | for name, value in md.Meta.items(): 112 | name = name.lower() 113 | metadata[name] = _process_metadata(name, value[0]) 114 | return content, metadata 115 | 116 | 117 | class HtmlReader(Reader): 118 | extension = "html" 119 | _re = re.compile('\<\!\-\-\#\s?[A-z0-9_-]*\s?\:s?[A-z0-9\s_-]*\s?\-\-\>') 120 | 121 | def read(self, filename): 122 | """Parse content and metadata of (x)HTML files""" 123 | content = open(filename) 124 | metadata = {'title':'unnamed'} 125 | for i in self._re.findall(content): 126 | key = i.split(':')[0][5:].strip() 127 | value = i.split(':')[-1][:-3].strip() 128 | name = key.lower() 129 | metadata[name] = _process_metadata(name, value) 130 | 131 | return content, metadata 132 | 133 | 134 | 135 | _EXTENSIONS = dict((cls.extension, cls) for cls in Reader.__subclasses__()) 136 | 137 | def read_file(filename, fmt=None, settings=None): 138 | """Return a reader object using the given format.""" 139 | if not fmt: 140 | fmt = filename.split('.')[-1] 141 | if fmt not in _EXTENSIONS.keys(): 142 | raise TypeError('Pelican does not know how to parse %s' % filename) 143 | reader = _EXTENSIONS[fmt]() 144 | settings_key = '%s_EXTENSIONS' % fmt.upper() 145 | if settings and settings_key in settings: 146 | reader.extensions = settings[settings_key] 147 | if not reader.enabled: 148 | raise ValueError("Missing dependencies for %s" % fmt) 149 | return reader.read(filename) 150 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Raclette.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Raclette.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Raclette" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Raclette" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /docs/_themes/pelican/static/pelican.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * pelican.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- pelican theme, based on the nature theme 6 | * 7 | * :copyright: Copyright 2011 by Alexis Metaireau. 8 | */ 9 | 10 | @import url("basic.css"); 11 | 12 | /* -- page layout ----------------------------------------------------------- */ 13 | 14 | body { 15 | font-family: Arial, sans-serif; 16 | font-size: 100%; 17 | background-color: white; 18 | color: #555; 19 | margin: 0; 20 | padding: 0; 21 | } 22 | 23 | div.documentwrapper { 24 | width: 70%; 25 | margin: auto; 26 | } 27 | 28 | div.bodywrapper { 29 | margin: 0 0 0 230px; 30 | } 31 | 32 | hr { 33 | border: 1px solid #B1B4B6; 34 | } 35 | 36 | div.document { 37 | } 38 | 39 | div.body { 40 | background-color: #ffffff; 41 | color: #3E4349; 42 | padding: 0 30px 30px 30px; 43 | font-size: 0.9em; 44 | } 45 | 46 | div.footer { 47 | color: #555; 48 | width: 100%; 49 | padding: 13px 0; 50 | text-align: center; 51 | font-size: 75%; 52 | } 53 | 54 | div.footer a { 55 | color: #444; 56 | text-decoration: underline; 57 | } 58 | 59 | div.related { 60 | background-color: #6BA81E; 61 | line-height: 32px; 62 | color: #fff; 63 | text-shadow: 0px 1px 0 #444; 64 | font-size: 0.9em; 65 | } 66 | 67 | div.related a { 68 | color: #E2F3CC; 69 | } 70 | 71 | div.sphinxsidebar { 72 | font-size: 0.75em; 73 | line-height: 1.5em; 74 | } 75 | 76 | div.sphinxsidebarwrapper{ 77 | padding: 20px 0; 78 | } 79 | 80 | div.sphinxsidebar h3, 81 | div.sphinxsidebar h4 { 82 | font-family: Arial, sans-serif; 83 | color: #222; 84 | font-size: 1.2em; 85 | font-weight: normal; 86 | margin: 0; 87 | padding: 5px 10px; 88 | background-color: #ddd; 89 | text-shadow: 1px 1px 0 white 90 | } 91 | 92 | div.sphinxsidebar h4{ 93 | font-size: 1.1em; 94 | } 95 | 96 | div.sphinxsidebar h3 a { 97 | color: #444; 98 | } 99 | 100 | 101 | div.sphinxsidebar p { 102 | color: #888; 103 | padding: 5px 20px; 104 | } 105 | 106 | div.sphinxsidebar p.topless { 107 | } 108 | 109 | div.sphinxsidebar ul { 110 | margin: 10px 20px; 111 | padding: 0; 112 | color: #000; 113 | } 114 | 115 | div.sphinxsidebar a { 116 | color: #444; 117 | } 118 | 119 | div.sphinxsidebar input { 120 | border: 1px solid #ccc; 121 | font-family: sans-serif; 122 | font-size: 1em; 123 | } 124 | 125 | div.sphinxsidebar input[type=text]{ 126 | margin-left: 20px; 127 | } 128 | 129 | /* -- body styles ----------------------------------------------------------- */ 130 | 131 | a { 132 | color: #005B81; 133 | text-decoration: none; 134 | } 135 | 136 | a:hover { 137 | color: #E32E00; 138 | text-decoration: underline; 139 | } 140 | 141 | div.body h1, 142 | div.body h2, 143 | div.body h3, 144 | div.body h4, 145 | div.body h5, 146 | div.body h6 { 147 | font-family: Arial, sans-serif; 148 | font-weight: normal; 149 | color: #212224; 150 | margin: 30px 0px 10px 0px; 151 | padding: 5px 0 5px 10px; 152 | text-shadow: 0px 1px 0 white 153 | } 154 | 155 | {% if theme_index_logo %} 156 | div.indexwrapper h1 { 157 | text-indent: -999999px; 158 | background: url({{ theme_index_logo }}) no-repeat center center; 159 | height: {{ theme_index_logo_height }}; 160 | } 161 | {% endif %} 162 | div.body h1 { 163 | border-top: 20px solid white; 164 | margin-top: 0; 165 | font-size: 250%; 166 | text-align: center; 167 | } 168 | 169 | div.body h2 { font-size: 150%; background-color: #C8D5E3; } 170 | div.body h3 { font-size: 120%; background-color: #D8DEE3; } 171 | div.body h4 { font-size: 110%; background-color: #D8DEE3; } 172 | div.body h5 { font-size: 100%; background-color: #D8DEE3; } 173 | div.body h6 { font-size: 100%; background-color: #D8DEE3; } 174 | 175 | a.headerlink { 176 | color: #c60f0f; 177 | font-size: 0.8em; 178 | padding: 0 4px 0 4px; 179 | text-decoration: none; 180 | } 181 | 182 | a.headerlink:hover { 183 | background-color: #c60f0f; 184 | color: white; 185 | } 186 | 187 | div.body p, div.body dd, div.body li { 188 | line-height: 1.5em; 189 | } 190 | 191 | div.admonition p.admonition-title + p { 192 | display: inline; 193 | } 194 | 195 | div.highlight{ 196 | background-color: #111; 197 | } 198 | 199 | div.note { 200 | background-color: #eee; 201 | border: 1px solid #ccc; 202 | } 203 | 204 | div.seealso { 205 | background-color: #ffc; 206 | border: 1px solid #ff6; 207 | } 208 | 209 | div.topic { 210 | background-color: #eee; 211 | } 212 | 213 | div.warning { 214 | background-color: #ffe4e4; 215 | border: 1px solid #f66; 216 | } 217 | 218 | p.admonition-title { 219 | display: inline; 220 | } 221 | 222 | p.admonition-title:after { 223 | content: ":"; 224 | } 225 | 226 | pre { 227 | padding: 10px; 228 | background-color: #111; 229 | color: #fff; 230 | line-height: 1.2em; 231 | border: 1px solid #C6C9CB; 232 | font-size: 1.1em; 233 | margin: 1.5em 0 1.5em 0; 234 | -webkit-box-shadow: 1px 1px 1px #d8d8d8; 235 | -moz-box-shadow: 1px 1px 1px #d8d8d8; 236 | } 237 | 238 | tt { 239 | background-color: #ecf0f3; 240 | color: #222; 241 | /* padding: 1px 2px; */ 242 | font-size: 1.1em; 243 | font-family: monospace; 244 | } 245 | 246 | .viewcode-back { 247 | font-family: Arial, sans-serif; 248 | } 249 | 250 | div.viewcode-block:target { 251 | background-color: #f4debf; 252 | border-top: 1px solid #ac9; 253 | border-bottom: 1px solid #ac9; 254 | } 255 | -------------------------------------------------------------------------------- /docs/pelican-themes.rst: -------------------------------------------------------------------------------- 1 | pelican-themes 2 | ############## 3 | 4 | 5 | 6 | Description 7 | =========== 8 | 9 | ``pelican-themes`` is a command line tool for managing themes for Pelican. 10 | 11 | 12 | Usage 13 | """"" 14 | 15 | | pelican-themes [-h] [-l] [-i theme path [theme path ...]] 16 | | [-r theme name [theme name ...]] 17 | | [-s theme path [theme path ...]] [-v] [--version] 18 | 19 | Optional arguments: 20 | """"""""""""""""""" 21 | 22 | 23 | -h, --help Show the help an exit 24 | 25 | -l, --list Show the themes already installed 26 | 27 | -i theme_path, --install theme_path One or more themes to install 28 | 29 | -r theme_name, --remove theme_name One or more themes to remove 30 | 31 | -s theme_path, --symlink theme_path Same as "--install", but create a symbolic link instead of copying the theme. 32 | Useful for theme development 33 | 34 | -v, --verbose Verbose output 35 | 36 | --version Print the version of this script 37 | 38 | 39 | 40 | Examples 41 | ======== 42 | 43 | 44 | Listing the installed themes 45 | """""""""""""""""""""""""""" 46 | 47 | With ``pelican-themes``, you can see the available themes by using the ``-l`` or ``--list`` option: 48 | 49 | .. code-block:: console 50 | 51 | $ pelican-themes -l 52 | notmyidea 53 | two-column@ 54 | simple 55 | $ pelican-themes --list 56 | notmyidea 57 | two-column@ 58 | simple 59 | 60 | In this example, we can see there is 3 themes available: ``notmyidea``, ``simple`` and ``two-column``. 61 | 62 | ``two-column`` is prefixed with an ``@`` because this theme is not copied to the Pelican theme path, but just linked to it (see `Creating symbolic links`_ for details about creating symbolic links). 63 | 64 | Note that you can combine the ``--list`` option with the ``-v`` or ``--verbose`` option to get a more verbose output, like this: 65 | 66 | .. code-block:: console 67 | 68 | $ pelican-themes -v -l 69 | /usr/local/lib/python2.6/dist-packages/pelican-2.6.0-py2.6.egg/pelican/themes/notmyidea 70 | /usr/local/lib/python2.6/dist-packages/pelican-2.6.0-py2.6.egg/pelican/themes/two-column (symbolic link to `/home/skami/Dev/Python/pelican-themes/two-column') 71 | /usr/local/lib/python2.6/dist-packages/pelican-2.6.0-py2.6.egg/pelican/themes/simple 72 | 73 | 74 | Installing themes 75 | """"""""""""""""" 76 | 77 | You can install one or more themes using the ``-i`` or ``--install`` option. 78 | This option takes as argument the path(s) of the theme(s) you want to install, and can be combined with the verbose option: 79 | 80 | .. code-block:: console 81 | 82 | # pelican-themes --install ~/Dev/Python/pelican-themes/notmyidea-cms --verbose 83 | 84 | .. code-block:: console 85 | 86 | # pelican-themes --install ~/Dev/Python/pelican-themes/notmyidea-cms\ 87 | ~/Dev/Python/pelican-themes/martyalchin \ 88 | --verbose 89 | 90 | .. code-block:: console 91 | 92 | # pelican-themes -vi ~/Dev/Python/pelican-themes/two-column 93 | 94 | 95 | Removing themes 96 | """"""""""""""" 97 | 98 | Pelican themes can also removes themes from the Pelican themes path. 99 | The ``-r`` or ``--remove`` takes as argument the name(s) of the theme(s) you want to remove, and can be combined with the ``--verbose`` option. 100 | 101 | .. code-block:: console 102 | 103 | # pelican-themes --remove two-column 104 | 105 | .. code-block:: console 106 | 107 | # pelican-themes -r martyachin notmyidea-cmd -v 108 | 109 | 110 | 111 | 112 | 113 | Creating symbolic links 114 | """"""""""""""""""""""" 115 | 116 | ``pelican-themes`` can also install themes by creating symbolic links instead of copying the whole themes in the Pelican themes path. 117 | 118 | To symbolically link a theme, you can use the ``-s`` or ``--symlink``, which works exactly as the ``--install`` option: 119 | 120 | .. code-block:: console 121 | 122 | # pelican-themes --symlink ~/Dev/Python/pelican-themes/two-column 123 | 124 | 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. 125 | 126 | This is useful for theme development: 127 | 128 | .. code-block:: console 129 | 130 | $ sudo pelican-themes -s ~/Dev/Python/pelican-themes/two-column 131 | $ pelican ~/Blog/content -o /tmp/out -t two-column 132 | $ firefox /tmp/out/index.html 133 | $ vim ~/Dev/Pelican/pelican-themes/two-coumn/static/css/main.css 134 | $ pelican ~/Blog/content -o /tmp/out -t two-column 135 | $ cp /tmp/bg.png ~/Dev/Pelican/pelican-themes/two-coumn/static/img/bg.png 136 | $ pelican ~/Blog/content -o /tmp/out -t two-column 137 | $ vim ~/Dev/Pelican/pelican-themes/two-coumn/templates/index.html 138 | $ pelican ~/Blog/content -o /tmp/out -t two-column 139 | 140 | 141 | 142 | Doing several things at once 143 | """""""""""""""""""""""""""" 144 | 145 | 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: 146 | 147 | 148 | .. code-block:: console 149 | 150 | # pelican-themes --remove notmyidea-cms two-column \ 151 | --install ~/Dev/Python/pelican-themes/notmyidea-cms-fr \ 152 | --symlink ~/Dev/Python/pelican-themes/two-column \ 153 | --verbose 154 | 155 | In this example, the theme ``notmyidea-cms`` is replaced by the theme ``notmyidea-cms-fr`` 156 | 157 | 158 | 159 | 160 | See also 161 | ======== 162 | 163 | - http://docs.notmyidea.org/alexis/pelican/ 164 | - ``/usr/share/doc/pelican/`` if you have installed Pelican using the `APT repository `_ 165 | 166 | 167 | -------------------------------------------------------------------------------- /docs/getting_started.rst: -------------------------------------------------------------------------------- 1 | Getting started 2 | ############### 3 | 4 | Installing 5 | ========== 6 | 7 | You're ready? Let's go ! You can install pelican in a lot of different ways, 8 | the simpler one is via `pip `_:: 9 | 10 | $ pip install pelican 11 | 12 | If you have the sources, you can install pelican using the distutils command 13 | install. I recommend to do so in a virtualenv:: 14 | 15 | $ virtualenv . 16 | $ source bin/activate 17 | $ python setup.py install 18 | 19 | Dependencies 20 | ------------ 21 | 22 | At this time, pelican is dependent of the following python packages: 23 | 24 | * feedgenerator, to generate the ATOM feeds. 25 | * jinja2, for templating support. 26 | 27 | If you're not using python 2.7, you will also need `argparse`. 28 | 29 | Optionally: 30 | 31 | * docutils, for reST support 32 | * pygments, to have syntactic colorization with resT input 33 | * Markdown, for Markdown as an input format 34 | 35 | Writing articles using pelican 36 | ============================== 37 | 38 | Files metadata 39 | -------------- 40 | 41 | Pelican tries to be smart enough to get the informations it needs from the 42 | file system (for instance, about the category of your articles), but you need to 43 | provide by hand some of those informations in your files. 44 | 45 | You could provide the metadata in the restructured text files, using the 46 | following syntax (give your file the `.rst` extension):: 47 | 48 | My super title 49 | ############## 50 | 51 | :date: 2010-10-03 10:20 52 | :tags: thats, awesome 53 | :category: yeah 54 | :author: Alexis Metaireau 55 | 56 | 57 | You can also use a markdown syntax (with a file ending in `.md`):: 58 | 59 | Date: 2010-12-03 60 | Title: My super title 61 | 62 | Put you content here. 63 | 64 | Note that none of those are mandatory: if the date is not specified, pelican will 65 | rely on the mtime of your file, and the category can also be determined by the 66 | directory where the rst file is. For instance, the category of 67 | `python/foobar/myfoobar.rst` is `foobar`. 68 | 69 | Generate your blog 70 | ------------------ 71 | 72 | To launch pelican, just use the `pelican` command:: 73 | 74 | $ pelican /path/to/your/content/ [-s path/to/your/settings.py] 75 | 76 | And… that's all! You can see your weblog generated on the `content/` folder. 77 | 78 | This one will just generate a simple output, with the default theme. It's not 79 | really sexy, as it's a simple HTML output (without any style). 80 | 81 | You can create your own style if you want, have a look to the help to see all 82 | the options you can use:: 83 | 84 | $ pelican --help 85 | 86 | Kickstart a blog 87 | ---------------- 88 | 89 | You also can use the `pelican-quickstart` script to start a new blog in 90 | seconds, by just answering few questions. Just run `pelican-quickstart` and 91 | you're done! (Added in pelican 3) 92 | 93 | Pages 94 | ----- 95 | 96 | If you create a folder named `pages`, all the files in it will be used to 97 | generate static pages. 98 | 99 | Then, use the `DISPLAY_PAGES_ON_MENU` setting, which will add all the pages to 100 | the menu. 101 | 102 | Importing an existing blog 103 | -------------------------- 104 | 105 | It is possible to import your blog from dotclear, wordpress and an RSS feed using 106 | a simple script. See :ref:`import`. 107 | 108 | Translations 109 | ------------ 110 | 111 | It is possible to translate articles. To do so, you need to add a `lang` meta 112 | in your articles/pages, and to set a `DEFAULT_LANG` setting (which is en by 113 | default). 114 | Then, only articles with this default language will be listed, and 115 | each article will have a translation list. 116 | 117 | Pelican uses the "slug" of two articles to compare if they are translations of 118 | each others. So it's possible to define (in restructured text) the slug 119 | directly. 120 | 121 | Here is an exemple of two articles (one in english and the other one in 122 | french). 123 | 124 | The english one:: 125 | 126 | Foobar is not dead 127 | ################## 128 | 129 | :slug: foobar-is-not-dead 130 | :lang: en 131 | 132 | That's true, foobar is still alive ! 133 | 134 | And the french one:: 135 | 136 | Foobar n'est pas mort ! 137 | ####################### 138 | 139 | :slug: foobar-is-not-dead 140 | :lang: fr 141 | 142 | Oui oui, foobar est toujours vivant ! 143 | 144 | Despite the text quality, you can see that only the slug is the same here. 145 | You're not forced to define the slug that way, and it's completely possible to 146 | have two translations with the same title (which defines the slug) 147 | 148 | Syntactic recognition 149 | --------------------- 150 | 151 | Pelican is able to regognise the syntax you are using, and to colorize the 152 | right way your block codes. To do so, you have to use the following syntax:: 153 | 154 | .. code-block:: identifier 155 | 156 | your code goes here 157 | 158 | The identifier is one of the lexers available `here 159 | `_. 160 | 161 | You also can use the default `::` syntax:: 162 | 163 | :: 164 | 165 | your code goes here 166 | 167 | It will be assumed that your code is witten in python. 168 | 169 | Autoreload 170 | ---------- 171 | 172 | It's possible to tell pelican to watch for your modifications, instead of 173 | manually launching it each time you need. Use the `-r` option, or 174 | `--autoreload`. 175 | 176 | Publishing drafts 177 | ----------------- 178 | 179 | If you want to publish an article as a draft, for friends to review it for 180 | instance, you can add a ``status: draft`` to its metadata, it will then be 181 | available under the ``drafts`` folder, and not be listed under the index page nor 182 | any category page. 183 | 184 | Viewing the generated files 185 | --------------------------- 186 | 187 | The files generated by pelican are static files, so you don't actually need 188 | something special to see what's hapenning with the generated files. 189 | 190 | You can either run your browser on the files on your disk:: 191 | 192 | $ firefox output/index.html 193 | 194 | Or run a simple web server using python:: 195 | 196 | cd output && python -m SimpleHTTPServer 197 | -------------------------------------------------------------------------------- /docs/fr/pelican-themes.rst: -------------------------------------------------------------------------------- 1 | pelican-themes 2 | ############## 3 | 4 | 5 | 6 | Description 7 | =========== 8 | 9 | ``pelican-themes`` est un outil en lignes de commandes pour gérer les thèmes de Pelican. 10 | 11 | 12 | Utilisation: 13 | """""""""""" 14 | 15 | | pelican-themes [-h] [-l] [-i *chemin d'un thème* [*chemin d'un thème* ...]] 16 | | [-r *nom d'un thème* [*nom d'un thème* ...]] 17 | | [-s *chemin d'un thème* [*chemin d'un thème* ...]] [-v] [--version] 18 | 19 | Arguments: 20 | """""""""" 21 | 22 | 23 | -h, --help Afficher l'aide et quitter 24 | 25 | -l, --list Montrer les thèmes installés 26 | 27 | -i chemin, --install chemin Chemin(s) d'accès d'un ou plusieurs thème à installer 28 | 29 | -r nom, --remove nom Noms d'un ou plusieurs thèmes à installer 30 | 31 | -s chemin, --symlink chemin Fonctionne de la même façon que l'option ``--install``, mais crée un lien symbolique au lieu d'effectuer une copie du thème vers le répertoire des thèmes. 32 | Utile pour le développement de thèmes. 33 | 34 | -v, --verbose Sortie détaillée 35 | 36 | --version Affiche la version du script et quitte 37 | 38 | 39 | 40 | Exemples 41 | ======== 42 | 43 | 44 | Lister les thèmes installés 45 | """"""""""""""""""""""""""" 46 | 47 | ``pelican-themes`` peut afficher les thèmes disponibles. 48 | 49 | Pour cela, vous pouvez utiliser l'option ``-l`` ou ``--list``, comme ceci: 50 | 51 | .. code-block:: console 52 | 53 | $ pelican-themes -l 54 | notmyidea 55 | two-column@ 56 | simple 57 | $ pelican-themes --list 58 | notmyidea 59 | two-column@ 60 | simple 61 | 62 | Dans cet exemple, nous voyons qu'il y a trois thèmes d'installés: ``notmyidea``, ``simple`` and ``two-column``. 63 | 64 | ``two-column`` est suivi d'un ``@`` par ce que c'est un lien symbolique (voir `Créer des liens symboliques`_). 65 | 66 | Notez que vous pouvez combiner l'option ``--list`` avec l'option ``--verbose``, pour afficher plus de détails: 67 | 68 | .. code-block:: console 69 | 70 | $ pelican-themes -v -l 71 | /usr/local/lib/python2.6/dist-packages/pelican-2.6.0-py2.6.egg/pelican/themes/notmyidea 72 | /usr/local/lib/python2.6/dist-packages/pelican-2.6.0-py2.6.egg/pelican/themes/two-column (symbolic link to `/home/skami/Dev/Python/pelican-themes/two-column') 73 | /usr/local/lib/python2.6/dist-packages/pelican-2.6.0-py2.6.egg/pelican/themes/simple 74 | 75 | 76 | Installer des thèmes 77 | """""""""""""""""""" 78 | 79 | Vous pouvez installer un ou plusieurs thèmes en utilisant l'option ``-i`` ou ``--install``. 80 | 81 | Cette option prends en argument le(s) chemin(s) d'accès du ou des thème(s) que vous voulez installer, et peut se combiner avec l'option ``--verbose``: 82 | 83 | .. code-block:: console 84 | 85 | # pelican-themes --install ~/Dev/Python/pelican-themes/notmyidea-cms --verbose 86 | 87 | .. code-block:: console 88 | 89 | # pelican-themes --install ~/Dev/Python/pelican-themes/notmyidea-cms\ 90 | ~/Dev/Python/pelican-themes/martyalchin \ 91 | --verbose 92 | 93 | .. code-block:: console 94 | 95 | # pelican-themes -vi ~/Dev/Python/pelican-themes/two-column 96 | 97 | 98 | Supprimer des thèmes 99 | """""""""""""""""""" 100 | 101 | ``pelican-themes`` peut aussi supprimer des thèmes précédemment installés grâce à l'option ``-r`` ou ``--remove``. 102 | 103 | Cette option prends en argument le ou les nom(s) des thèmes que vous voulez installer, et peux se combiner avec l'option ``--verbose``: 104 | 105 | .. code-block:: console 106 | 107 | # pelican-themes --remove two-column 108 | 109 | .. code-block:: console 110 | 111 | # pelican-themes -r martyachin notmyidea-cmd -v 112 | 113 | 114 | 115 | 116 | 117 | Créer des liens symboliques 118 | """"""""""""""""""""""""""" 119 | 120 | 121 | L'option ``-s`` ou ``--symlink`` de ``pelican-themes`` permet de lier symboliquement un thème. 122 | 123 | Cette option s'utilise exactement comme l'option ``--install``: 124 | 125 | .. code-block:: console 126 | 127 | # pelican-themes --symlink ~/Dev/Python/pelican-themes/two-column 128 | 129 | Dans l'exemple ci dessus, un lien symbolique pointant vers le thème ``two-column`` a été installé dans le répertoire des thèmes de Pelican, toute modification sur le thème ``two-column`` prendra donc effet immédiatement. 130 | 131 | Cela peut être pratique pour le développement de thèmes 132 | 133 | .. code-block:: console 134 | 135 | $ sudo pelican-themes -s ~/Dev/Python/pelican-themes/two-column 136 | $ pelican ~/Blog/content -o /tmp/out -t two-column 137 | $ firefox /tmp/out/index.html 138 | $ vim ~/Dev/Pelican/pelican-themes/two-coumn/static/css/main.css 139 | $ pelican ~/Blog/content -o /tmp/out -t two-column 140 | $ cp /tmp/bg.png ~/Dev/Pelican/pelican-themes/two-coumn/static/img/bg.png 141 | $ pelican ~/Blog/content -o /tmp/out -t two-column 142 | $ vim ~/Dev/Pelican/pelican-themes/two-coumn/templates/index.html 143 | $ pelican ~/Blog/content -o /tmp/out -t two-column 144 | 145 | 146 | Notez que cette fonctionnalité nécessite d'avoir un système d'exploitation et un système de fichiers supportant les liens symboliques, elle n'est donc pas disponible sous Micro$oft®©™ Fenêtre®©™. 147 | 148 | Faire plusieurs choses à la fois 149 | """""""""""""""""""""""""""""""" 150 | 151 | 152 | Les options ``--install``, ``--remove`` et ``--symlink`` peuvent être employées en même temps, ce qui permets de réaliser plusieurs opérations en même temps: 153 | 154 | .. code-block:: console 155 | 156 | # pelican-themes --remove notmyidea-cms two-column \ 157 | --install ~/Dev/Python/pelican-themes/notmyidea-cms-fr \ 158 | --symlink ~/Dev/Python/pelican-themes/two-column \ 159 | --verbose 160 | 161 | Dans cette exemple, le thème ``notmyidea-cms`` sera remplacé par le thème ``notmyidea-cms-fr`` et le thème ``two-column`` sera lié symboliquement... 162 | 163 | 164 | 165 | À voir également 166 | ================ 167 | 168 | - http://docs.notmyidea.org/alexis/pelican/ 169 | - ``/usr/share/doc/pelican/`` si vous avez installé Pelican par le `dépôt APT `_ 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /pelican/__init__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import time 4 | 5 | from pelican.generators import (ArticlesGenerator, PagesGenerator, 6 | StaticGenerator, PdfGenerator) 7 | from pelican.settings import read_settings 8 | from pelican.utils import clean_output_dir, files_changed 9 | from pelican.writers import Writer 10 | from pelican import log 11 | 12 | __version__ = "2.7.2" 13 | 14 | 15 | class Pelican(object): 16 | def __init__(self, settings=None, path=None, theme=None, output_path=None, 17 | markup=None, delete_outputdir=False): 18 | """Read the settings, and performs some checks on the environment 19 | before doing anything else. 20 | """ 21 | self.path = path or settings['PATH'] 22 | if not self.path: 23 | raise Exception('you need to specify a path containing the content' 24 | ' (see pelican --help for more information)') 25 | 26 | if self.path.endswith('/'): 27 | self.path = self.path[:-1] 28 | 29 | # define the default settings 30 | self.settings = settings 31 | self.theme = theme or settings['THEME'] 32 | output_path = output_path or settings['OUTPUT_PATH'] 33 | self.output_path = os.path.realpath(output_path) 34 | self.markup = markup or settings['MARKUP'] 35 | self.delete_outputdir = delete_outputdir or settings['DELETE_OUTPUT_DIRECTORY'] 36 | 37 | # find the theme in pelican.theme if the given one does not exists 38 | if not os.path.exists(self.theme): 39 | theme_path = os.sep.join([os.path.dirname( 40 | os.path.abspath(__file__)), "themes/%s" % self.theme]) 41 | if os.path.exists(theme_path): 42 | self.theme = theme_path 43 | else: 44 | raise Exception("Impossible to find the theme %s" % theme) 45 | 46 | def run(self): 47 | """Run the generators and return""" 48 | 49 | context = self.settings.copy() 50 | generators = [ 51 | cls( 52 | context, 53 | self.settings, 54 | self.path, 55 | self.theme, 56 | self.output_path, 57 | self.markup, 58 | self.delete_outputdir 59 | ) for cls in self.get_generator_classes() 60 | ] 61 | 62 | for p in generators: 63 | if hasattr(p, 'generate_context'): 64 | p.generate_context() 65 | 66 | # erase the directory if it is not the source and if that's 67 | # explicitely asked 68 | if (self.delete_outputdir and 69 | os.path.realpath(self.path).startswith(self.output_path)): 70 | clean_output_dir(self.output_path) 71 | 72 | writer = self.get_writer() 73 | 74 | for p in generators: 75 | if hasattr(p, 'generate_output'): 76 | p.generate_output(writer) 77 | 78 | 79 | def get_generator_classes(self): 80 | generators = [ArticlesGenerator, PagesGenerator, StaticGenerator] 81 | if self.settings['PDF_GENERATOR']: 82 | generators.append(PdfGenerator) 83 | return generators 84 | 85 | def get_writer(self): 86 | return Writer(self.output_path, settings=self.settings) 87 | 88 | 89 | 90 | def main(): 91 | parser = argparse.ArgumentParser(description="""A tool to generate a 92 | static blog, with restructured text input files.""") 93 | 94 | parser.add_argument(dest='path', nargs='?', 95 | help='Path where to find the content files') 96 | parser.add_argument('-t', '--theme-path', dest='theme', 97 | help='Path where to find the theme templates. If not specified, it' 98 | 'will use the default one included with pelican.') 99 | parser.add_argument('-o', '--output', dest='output', 100 | help='Where to output the generated files. If not specified, a directory' 101 | ' will be created, named "output" in the current path.') 102 | parser.add_argument('-m', '--markup', default=None, dest='markup', 103 | help='the list of markup language to use (rst or md). Please indicate ' 104 | 'them separated by commas') 105 | parser.add_argument('-s', '--settings', dest='settings', default='', 106 | help='the settings of the application. Default to False.') 107 | parser.add_argument('-d', '--delete-output-directory', dest='delete_outputdir', 108 | action='store_true', help='Delete the output directory.') 109 | parser.add_argument('-v', '--verbose', action='store_const', const=log.INFO, dest='verbosity', 110 | help='Show all messages') 111 | parser.add_argument('-q', '--quiet', action='store_const', const=log.CRITICAL, dest='verbosity', 112 | help='Show only critical errors') 113 | parser.add_argument('-D', '--debug', action='store_const', const=log.DEBUG, dest='verbosity', 114 | help='Show all message, including debug messages') 115 | parser.add_argument('--version', action='version', version=__version__, 116 | help='Print the pelican version and exit') 117 | parser.add_argument('-r', '--autoreload', dest='autoreload', action='store_true', 118 | help="Relaunch pelican each time a modification occurs on the content" 119 | "files") 120 | args = parser.parse_args() 121 | 122 | log.init(args.verbosity) 123 | # Split the markup languages only if some have been given. Otherwise, populate 124 | # the variable with None. 125 | markup = [a.strip().lower() for a in args.markup.split(',')] if args.markup else None 126 | 127 | settings = read_settings(args.settings) 128 | 129 | cls = settings.get('PELICAN_CLASS') 130 | if isinstance(cls, basestring): 131 | module, cls_name = cls.rsplit('.', 1) 132 | module = __import__(module) 133 | cls = getattr(module, cls_name) 134 | 135 | try: 136 | pelican = cls(settings, args.path, args.theme, args.output, markup, 137 | args.delete_outputdir) 138 | if args.autoreload: 139 | while True: 140 | try: 141 | # Check source dir for changed files ending with the given 142 | # extension in the settings. In the theme dir is no such 143 | # restriction; all files are recursively checked if they 144 | # have changed, no matter what extension the filenames 145 | # have. 146 | if files_changed(pelican.path, pelican.markup) or \ 147 | files_changed(pelican.theme, ['']): 148 | pelican.run() 149 | time.sleep(.5) # sleep to avoid cpu load 150 | except KeyboardInterrupt: 151 | break 152 | else: 153 | pelican.run() 154 | except Exception, e: 155 | log.critical(unicode(e)) 156 | 157 | 158 | if __name__ == '__main__': 159 | main() 160 | -------------------------------------------------------------------------------- /tools/pelican-themes: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os, sys, shutil 5 | import argparse 6 | 7 | try: 8 | import pelican 9 | except: 10 | err('Cannot import pelican.\nYou must install Pelican in order to run this script.', -1) 11 | 12 | 13 | global _THEMES_PATH 14 | _THEMES_PATH = os.path.join( 15 | os.path.dirname( 16 | os.path.abspath( 17 | pelican.__file__ 18 | ) 19 | ), 20 | 'themes' 21 | ) 22 | 23 | __version__ = '0.2' 24 | _BUILTIN_THEMES = ['simple', 'notmyidea'] 25 | 26 | 27 | def err(msg, die=None): 28 | """Print an error message and exits if an exit code is given""" 29 | sys.stderr.write(str(msg) + '\n') 30 | if die: 31 | sys.exit((die if type(die) is int else 1)) 32 | 33 | 34 | def main(): 35 | """Main function""" 36 | 37 | parser = argparse.ArgumentParser(description="""Install themes for Pelican""") 38 | 39 | excl= parser.add_mutually_exclusive_group() 40 | excl.add_argument('-l', '--list', dest='action', action="store_const", const='list', 41 | help="Show the themes already installed and exit") 42 | excl.add_argument('-p', '--path', dest='action', action="store_const", const='path', 43 | help="Show the themes path and exit") 44 | excl.add_argument('-V', '--version', action='version', version='pelican-themes v{0}'.format(__version__), 45 | help='Print the version of this script') 46 | 47 | 48 | parser.add_argument('-i', '--install', dest='to_install', nargs='+', metavar="theme path", 49 | help='The themes to install ') 50 | parser.add_argument('-r', '--remove', dest='to_remove', nargs='+', metavar="theme name", 51 | help='The themes to remove') 52 | parser.add_argument('-s', '--symlink', dest='to_symlink', nargs='+', metavar="theme path", 53 | help="Same as `--install', but create a symbolic link instead of copying the theme. Useful for theme development") 54 | parser.add_argument('-c', '--clean', dest='clean', action="store_true", 55 | help="Remove the broken symbolic links of the theme path") 56 | 57 | 58 | parser.add_argument('-v', '--verbose', dest='verbose', action="store_true", 59 | help="Verbose output") 60 | 61 | 62 | args = parser.parse_args() 63 | 64 | 65 | if args.action: 66 | if args.action is 'list': 67 | list_themes(args.verbose) 68 | elif args.action is 'path': 69 | print(_THEMES_PATH) 70 | elif args.to_install or args.to_remove or args.to_symlink or args.clean: 71 | 72 | if args.to_remove: 73 | if args.verbose: 74 | print('Removing themes...') 75 | 76 | for i in args.to_remove: 77 | remove(i, v=args.verbose) 78 | 79 | if args.to_install: 80 | if args.verbose: 81 | print('Installing themes...') 82 | 83 | for i in args.to_install: 84 | install(i, v=args.verbose) 85 | 86 | if args.to_symlink: 87 | if args.verbose: 88 | print('Linking themes...') 89 | 90 | for i in args.to_symlink: 91 | symlink(i, v=args.verbose) 92 | 93 | if args.clean: 94 | if args.verbose: 95 | print('Cleaning the themes directory...') 96 | 97 | clean(v=args.verbose) 98 | else: 99 | print('No argument given... exiting.') 100 | 101 | 102 | def themes(): 103 | """Returns the list of the themes""" 104 | for i in os.listdir(_THEMES_PATH): 105 | e = os.path.join(_THEMES_PATH, i) 106 | 107 | if os.path.isdir(e): 108 | if os.path.islink(e): 109 | yield (e, os.readlink(e)) 110 | else: 111 | yield (e, None) 112 | 113 | 114 | def list_themes(v=False): 115 | """Display the list of the themes""" 116 | for t, l in themes(): 117 | if not v: 118 | t = os.path.basename(t) 119 | if l: 120 | if v: 121 | print(t + (" (symbolic link to `" + l + "')")) 122 | else: 123 | print(t + '@') 124 | else: 125 | print(t) 126 | 127 | 128 | def remove(theme_name, v=False): 129 | """Removes a theme""" 130 | 131 | theme_name = theme_name.replace('/','') 132 | target = os.path.join(_THEMES_PATH, theme_name) 133 | 134 | if theme_name in _BUILTIN_THEMES: 135 | err(theme_name + ' is a builtin theme.\nYou cannot remove a builtin theme with this script, remove it by hand if you want.') 136 | elif os.path.islink(target): 137 | if v: 138 | print('Removing link `' + target + "'") 139 | os.remove(target) 140 | elif os.path.isdir(target): 141 | if v: 142 | print('Removing directory `' + target + "'") 143 | shutil.rmtree(target) 144 | elif os.path.exists(target): 145 | err(target + ' : not a valid theme') 146 | else: 147 | err(target + ' : no such file or directory') 148 | 149 | 150 | def install(path, v=False): 151 | """Installs a theme""" 152 | if not os.path.exists(path): 153 | err(path + ' : no such file or directory') 154 | elif not os.path.isdir(path): 155 | err(path + ' : no a directory') 156 | else: 157 | theme_name = os.path.basename(os.path.normpath(path)) 158 | theme_path = os.path.join(_THEMES_PATH, theme_name) 159 | if os.path.exists(theme_path): 160 | err(path + ' : already exists') 161 | else: 162 | if v: 163 | print("Copying `{p}' to `{t}' ...".format(p=path, t=theme_path)) 164 | try: 165 | shutil.copytree(path, theme_path) 166 | except Exception, e: 167 | err("Cannot copy `{p}' to `{t}':\n{e}".format(p=path, t=theme_path, e=str(e))) 168 | 169 | 170 | def symlink(path, v=False): 171 | """Symbolically link a theme""" 172 | if not os.path.exists(path): 173 | err(path + ' : no such file or directory') 174 | elif not os.path.isdir(path): 175 | err(path + ' : no a directory') 176 | else: 177 | theme_name = os.path.basename(os.path.normpath(path)) 178 | theme_path = os.path.join(_THEMES_PATH, theme_name) 179 | if os.path.exists(theme_path): 180 | err(path + ' : already exists') 181 | else: 182 | if v: 183 | print("Linking `{p}' to `{t}' ...".format(p=path, t=theme_path)) 184 | try: 185 | os.symlink(path, theme_path) 186 | except Exception, e: 187 | err("Cannot link `{p}' to `{t}':\n{e}".format(p=path, t=theme_path, e=str(e))) 188 | 189 | 190 | def is_broken_link(path): 191 | """Returns True if the path given as is a broken symlink""" 192 | path = os.readlink(path) 193 | return not os.path.exists(path) 194 | 195 | 196 | def clean(v=False): 197 | """Removes the broken symbolic links""" 198 | c=0 199 | for path in os.listdir(_THEMES_PATH): 200 | path = os.path.join(_THEMES_PATH, path) 201 | if os.path.islink(path): 202 | if is_broken_link(path): 203 | if v: 204 | print('Removing {0}'.format(path)) 205 | try: 206 | os.remove(path) 207 | except OSError, e: 208 | print('Error: cannot remove {0}'.format(path)) 209 | else: 210 | c+=1 211 | 212 | print("\nRemoved {0} broken links".format(c)) 213 | 214 | if __name__ == '__main__': 215 | main() 216 | -------------------------------------------------------------------------------- /docs/fr/themes.rst: -------------------------------------------------------------------------------- 1 | .. _theming-pelican: 2 | 3 | Cette page est une traduction de la documentation originale, en anglais et 4 | disponible `ici <../themes.html>`_. 5 | 6 | Comment créer des thèmes pour Pelican 7 | ##################################### 8 | 9 | Pelican utlise le très bon moteur de template `jinja2 `_ 10 | pour produire de l'HTML. La syntaxe de jinja2 est vraiment très simple. Si vous 11 | voulez créer votre propre thème, soyez libre de prendre inspiration sur le theme 12 | "simple" qui est disponible `ici 13 | `_ 14 | 15 | Structure 16 | ========= 17 | 18 | Pour réaliser votre propre thème vous devez respecter la structure suivante :: 19 | 20 | ├── static 21 | │   ├── css 22 | │   └── images 23 | └── templates 24 | ├── archives.html // pour afficher les archives 25 | ├── article.html // généré pour chaque article 26 | ├── categories.html // doit lister toutes les catégories 27 | ├── category.html // généré pour chaque catégorie 28 | ├── index.html // la page d'index, affiche tous les articles 29 | ├── page.html // généré pour chaque page 30 | ├── tag.html // généré pour chaque tag 31 | └── tags.html // doit lister tous les tags. Peut être un nuage de tag. 32 | 33 | 34 | * `static` contient tout le contenu statique. Il sera copié dans le dossier 35 | `theme/static`. J'ai mis un dossier css et un image, mais ce sont juste des 36 | exemples. Mettez ce dont vous avez besoin ici. 37 | 38 | * `templates` contient tous les templates qui vont être utiliser pour générer les 39 | pages. J'ai juste mis les templates obligatoires ici, vous pouvez définir les 40 | vôtres si cela vous aide à vous organiser pendant que vous réaliser le thème. 41 | Vous pouvez par exemple utiliser les directives {% include %} et {% extends %} 42 | de jinja2. 43 | 44 | Templates et variables 45 | ====================== 46 | 47 | Cela utilise une syntaxe simple, que vous pouvez insérer dans vos pages HTML. 48 | Ce document décrit les templates qui doivent exister dans un thème, et quelles 49 | variables seront passées à chaque template, au moment de le générer. 50 | 51 | Tous les templates recevront les variables définies dans votre fichier de 52 | configuration, si elles sont en capitales. Vous pouvez y accéder directement. 53 | 54 | Variables communes 55 | ------------------ 56 | 57 | Toutes ces variables seront passées à chaque template. 58 | 59 | ============= =================================================== 60 | Variable Description 61 | ============= =================================================== 62 | articles C'est la liste des articles, ordonnée décroissante 63 | par date. Tous les éléments de la liste sont des 64 | objets `Article`, vous pouvez donc accéder à leurs 65 | propriétés (exemple : title, summary, author, etc). 66 | dates La même liste d'articles, ordonnée croissante par 67 | date. 68 | tags Un dictionnaire contenant tous les tags (clés), et 69 | la liste des articles correspondants à chacun 70 | d'entre eux (valeur). 71 | categories Un dictionnaire contenant toutes les catégories 72 | (clés), et la liste des articles correspondants à 73 | chacune d'entre elles (valeur). 74 | pages La liste des pages. 75 | ============= =================================================== 76 | 77 | index.html 78 | ---------- 79 | 80 | La page d'accueil de votre blog, sera générée dans output/index.html. 81 | 82 | Si la pagination est activée, les pages suivantes seront à l'adresse 83 | output/index`n`.html. 84 | 85 | =================== =================================================== 86 | Variable Description 87 | =================== =================================================== 88 | articles_paginator Un objet paginator de la liste d'articles. 89 | articles_page La page actuelle d'articles. 90 | dates_paginator Un objet paginator de la liste d'articles, ordonné 91 | par date croissante. 92 | dates_pages La page actuelle d'articles, ordonnée par date 93 | croissante. 94 | page_name 'index'. 95 | =================== =================================================== 96 | 97 | category.html 98 | ------------- 99 | 100 | Ce template sera généré pour chaque catégorie existante, et se retrouvera 101 | finalement à output/category/`nom de la catégorie`.html. 102 | 103 | Si la pagination est activée, les pages suivantes seront disponibles à 104 | l'adresse output/category/`nom de la catégorie``n`.html. 105 | 106 | =================== =================================================== 107 | Variable Description 108 | =================== =================================================== 109 | category La catégorie qui est en train d'être générée. 110 | articles Les articles dans cette catégorie. 111 | dates Les articles dans cette catégorie, ordonnés par 112 | date croissante. 113 | articles_paginator Un objet paginator de la liste d'articles. 114 | articles_page La page actuelle d'articles. 115 | dates_paginator Un objet paginator de la liste d'articles, ordonné 116 | par date croissante. 117 | dates_pages La page actuelle d'articles, ordonnée par date 118 | croissante. 119 | page_name 'category/`nom de la catégorie`'. 120 | =================== =================================================== 121 | 122 | article.html 123 | ------------- 124 | 125 | Ce template sera généré pour chaque article. Les fichiers .html seront 126 | disponibles à output/`nom de l'article`.html. 127 | 128 | ============= =================================================== 129 | Variable Description 130 | ============= =================================================== 131 | article L'objet article à afficher. 132 | category Le nom de la catégorie de l'article actuel. 133 | ============= =================================================== 134 | 135 | page.html 136 | --------- 137 | 138 | Pour chaque page ce template sera généré à l'adresse 139 | output/`nom de la page`.html 140 | 141 | ============= =================================================== 142 | Variable Description 143 | ============= =================================================== 144 | page L'objet page à afficher. Vous pouvez accéder à son 145 | titre (title), slug, et son contenu (content). 146 | ============= =================================================== 147 | 148 | tag.html 149 | -------- 150 | 151 | Ce template sera généré pour chaque tag. Cela créera des fichiers .html à 152 | l'adresse output/tag/`nom du tag`.html. 153 | 154 | Si la pagination est activée, les pages suivantes seront disponibles à 155 | l'adresse output/tag/`nom du tag``n`.html 156 | 157 | =================== =================================================== 158 | Variable Description 159 | =================== =================================================== 160 | tag Nom du tag à afficher. 161 | articles Une liste des articles contenant ce tag. 162 | dates Une liste des articles contenant ce tag, ordonnée 163 | par date croissante. 164 | articles_paginator Un objet paginator de la liste d'articles. 165 | articles_page La page actuelle d'articles. 166 | dates_paginator Un objet paginator de la liste d'articles, ordonné 167 | par date croissante. 168 | dates_pages La page actuelle d'articles, ordonnée par date 169 | croissante. 170 | page_name 'tag/`nom du tag`'. 171 | =================== =================================================== 172 | -------------------------------------------------------------------------------- /pelican/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import re 3 | import os 4 | import shutil 5 | import time 6 | import calendar 7 | import pytz 8 | from datetime import datetime 9 | from codecs import open as _open 10 | from itertools import groupby 11 | from operator import attrgetter 12 | from pelican.log import warning, info 13 | 14 | 15 | def get_date(string): 16 | """Return a datetime object from a string. 17 | 18 | If no format matches the given date, raise a ValuEerror 19 | """ 20 | formats = ['%Y-%m-%d %H:%M', '%Y/%m/%d %H:%M', '%Y-%m-%d', '%Y/%m/%d', 21 | '%d/%m/%Y', '%d.%m.%Y', '%d.%m.%Y %H:%M', '%Y-%m-%d %H:%M:%S'] 22 | for date_format in formats: 23 | try: 24 | return datetime.strptime(string, date_format) 25 | except ValueError: 26 | pass 27 | raise ValueError("'%s' is not a valid date" % string) 28 | 29 | 30 | def open(filename): 31 | """Open a file and return it's content""" 32 | return _open(filename, encoding='utf-8').read() 33 | 34 | 35 | def slugify(value): 36 | """ 37 | Normalizes string, converts to lowercase, removes non-alpha characters, 38 | and converts spaces to hyphens. 39 | 40 | Took from django sources. 41 | """ 42 | if type(value) == unicode: 43 | import unicodedata 44 | value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') 45 | value = unicode(re.sub('[^\w\s-]', '', value).strip().lower()) 46 | return re.sub('[-\s]+', '-', value) 47 | 48 | def copy(path, source, destination, destination_path=None, overwrite=False): 49 | """Copy path from origin to destination. 50 | 51 | The function is able to copy either files or directories. 52 | 53 | :param path: the path to be copied from the source to the destination 54 | :param source: the source dir 55 | :param destination: the destination dir 56 | :param destination_path: the destination path (optional) 57 | :param overwrite: wether to overwrite the destination if already exists or not 58 | 59 | """ 60 | if not destination_path: 61 | destination_path = path 62 | 63 | source_ = os.path.abspath(os.path.expanduser(os.path.join(source, path))) 64 | destination_ = os.path.abspath( 65 | os.path.expanduser(os.path.join(destination, destination_path))) 66 | 67 | if os.path.isdir(source_): 68 | try: 69 | shutil.copytree(source_, destination_) 70 | info('copying %s to %s' % (source_, destination_)) 71 | except OSError: 72 | if overwrite: 73 | shutil.rmtree(destination_) 74 | shutil.copytree(source_, destination_) 75 | info('replacement of %s with %s' % (source_, destination_)) 76 | 77 | elif os.path.isfile(source_): 78 | shutil.copy(source_, destination_) 79 | info('copying %s to %s' % (source_, destination_)) 80 | 81 | def clean_output_dir(path): 82 | """Remove all the files from the output directory""" 83 | 84 | # remove all the existing content from the output folder 85 | try: 86 | shutil.rmtree(path) 87 | except Exception: 88 | pass 89 | 90 | 91 | def get_relative_path(filename): 92 | """Return the relative path to the given filename""" 93 | return '../' * filename.count('/') + '.' 94 | 95 | 96 | def truncate_html_words(s, num, end_text='...'): 97 | """Truncates HTML to a certain number of words (not counting tags and 98 | comments). Closes opened tags if they were correctly closed in the given 99 | html. Takes an optional argument of what should be used to notify that the 100 | string has been truncated, defaulting to ellipsis (...). 101 | 102 | Newlines in the HTML are preserved. 103 | From the django framework. 104 | """ 105 | length = int(num) 106 | if length <= 0: 107 | return u'' 108 | html4_singlets = ('br', 'col', 'link', 'base', 'img', 'param', 'area', 'hr', 'input') 109 | 110 | # Set up regular expressions 111 | re_words = re.compile(r'&.*?;|<.*?>|(\w[\w-]*)', re.U) 112 | re_tag = re.compile(r'<(/)?([^ ]+?)(?: (/)| .*?)?>') 113 | # Count non-HTML words and keep note of open tags 114 | pos = 0 115 | end_text_pos = 0 116 | words = 0 117 | open_tags = [] 118 | while words <= length: 119 | m = re_words.search(s, pos) 120 | if not m: 121 | # Checked through whole string 122 | break 123 | pos = m.end(0) 124 | if m.group(1): 125 | # It's an actual non-HTML word 126 | words += 1 127 | if words == length: 128 | end_text_pos = pos 129 | continue 130 | # Check for tag 131 | tag = re_tag.match(m.group(0)) 132 | if not tag or end_text_pos: 133 | # Don't worry about non tags or tags after our truncate point 134 | continue 135 | closing_tag, tagname, self_closing = tag.groups() 136 | tagname = tagname.lower() # Element names are always case-insensitive 137 | if self_closing or tagname in html4_singlets: 138 | pass 139 | elif closing_tag: 140 | # Check for match in open tags list 141 | try: 142 | i = open_tags.index(tagname) 143 | except ValueError: 144 | pass 145 | else: 146 | # SGML: An end tag closes, back to the matching start tag, all unclosed intervening start tags with omitted end tags 147 | open_tags = open_tags[i+1:] 148 | else: 149 | # Add it to the start of the open tags list 150 | open_tags.insert(0, tagname) 151 | if words <= length: 152 | # Don't try to close tags if we don't need to truncate 153 | return s 154 | out = s[:end_text_pos] 155 | if end_text: 156 | out += ' ' + end_text 157 | # Close any tags still open 158 | for tag in open_tags: 159 | out += '' % tag 160 | # Return string 161 | return out 162 | 163 | 164 | def process_translations(content_list): 165 | """ Finds all translation and returns 166 | tuple with two lists (index, translations). 167 | Index list includes items in default language 168 | or items which have no variant in default language. 169 | 170 | Also, for each content_list item, it 171 | sets attribute 'translations' 172 | """ 173 | content_list.sort(key=attrgetter('slug')) 174 | grouped_by_slugs = groupby(content_list, attrgetter('slug')) 175 | index = [] 176 | translations = [] 177 | 178 | for slug, items in grouped_by_slugs: 179 | items = list(items) 180 | # find items with default language 181 | default_lang_items = filter( 182 | attrgetter('in_default_lang'), 183 | items 184 | ) 185 | len_ = len(default_lang_items) 186 | if len_ > 1: 187 | warning(u'there are %s variants of "%s"' % (len_, slug)) 188 | for x in default_lang_items: 189 | warning(' %s' % x.filename) 190 | elif len_ == 0: 191 | default_lang_items = items[:1] 192 | 193 | if not slug: 194 | warning('empty slug for %r' %( default_lang_items[0].filename,)) 195 | index.extend(default_lang_items) 196 | translations.extend(filter( 197 | lambda x: x not in default_lang_items, 198 | items 199 | )) 200 | for a in items: 201 | a.translations = filter(lambda x: x != a, items) 202 | return index, translations 203 | 204 | 205 | LAST_MTIME = 0 206 | 207 | 208 | def files_changed(path, extensions): 209 | """Return True if the files have changed since the last check""" 210 | 211 | def with_extension(f): 212 | return any(f.endswith(ext) for ext in extensions) 213 | 214 | def file_times(path): 215 | """Return the last time files have been modified""" 216 | for root, dirs, files in os.walk(path): 217 | dirs[:] = [x for x in dirs if x[0] != '.'] 218 | for file in files: 219 | if any(file.endswith(ext) for ext in extensions): 220 | yield os.stat(os.path.join(root, file)).st_mtime 221 | 222 | global LAST_MTIME 223 | mtime = max(file_times(path)) 224 | if mtime > LAST_MTIME: 225 | LAST_MTIME = mtime 226 | return True 227 | return False 228 | 229 | def set_date_tzinfo(d, tz_name=None): 230 | """ Date without tzinfo shoudbe utc. 231 | This function set the right tz to date that aren't utc and don't have tzinfo 232 | """ 233 | if tz_name is not None: 234 | tz = pytz.timezone(tz_name) 235 | return tz.localize(d) 236 | else: 237 | return d 238 | -------------------------------------------------------------------------------- /pelican/writers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import with_statement 3 | import os 4 | import re 5 | from codecs import open 6 | from functools import partial 7 | import locale 8 | 9 | from feedgenerator import Atom1Feed, Rss201rev2Feed 10 | from pelican.utils import get_relative_path, set_date_tzinfo 11 | from pelican.paginator import Paginator 12 | from pelican.log import * 13 | 14 | 15 | class Writer(object): 16 | 17 | def __init__(self, output_path, settings=None): 18 | self.output_path = output_path 19 | self.reminder = dict() 20 | self.settings = settings or {} 21 | 22 | def _create_new_feed(self, feed_type, context): 23 | feed_class = Rss201rev2Feed if feed_type == 'rss' else Atom1Feed 24 | feed = feed_class( 25 | title=context['SITENAME'], 26 | link=self.site_url, 27 | feed_url=self.feed_url, 28 | description=context.get('SITESUBTITLE', '')) 29 | return feed 30 | 31 | 32 | def _add_item_to_the_feed(self, feed, item): 33 | 34 | feed.add_item( 35 | title=item.title, 36 | link='%s/%s' % (self.site_url, item.url), 37 | description=item.content, 38 | categories=item.tags if hasattr(item, 'tags') else None, 39 | author_name=getattr(item, 'author', 'John Doe'), 40 | pubdate=set_date_tzinfo(item.date, 41 | self.settings.get('TIMEZONE', None))) 42 | 43 | def write_feed(self, elements, context, filename=None, feed_type='atom'): 44 | """Generate a feed with the list of articles provided 45 | 46 | Return the feed. If no output_path or filename is specified, just return 47 | the feed object. 48 | 49 | :param elements: the articles to put on the feed. 50 | :param context: the context to get the feed metadata. 51 | :param filename: the filename to output. 52 | :param feed_type: the feed type to use (atom or rss) 53 | """ 54 | old_locale = locale.setlocale(locale.LC_ALL) 55 | locale.setlocale(locale.LC_ALL, 'C') 56 | try: 57 | self.site_url = context.get('SITEURL', get_relative_path(filename)) 58 | self.feed_url= '%s/%s' % (self.site_url, filename) 59 | 60 | feed = self._create_new_feed(feed_type, context) 61 | 62 | max_items = len(elements) 63 | if self.settings['FEED_MAX_ITEMS']: 64 | max_items = min(self.settings['FEED_MAX_ITEMS'], max_items) 65 | for i in xrange(max_items): 66 | self._add_item_to_the_feed(feed, elements[i]) 67 | 68 | if filename: 69 | complete_path = os.path.join(self.output_path, filename) 70 | try: 71 | os.makedirs(os.path.dirname(complete_path)) 72 | except Exception: 73 | pass 74 | fp = open(complete_path, 'w') 75 | feed.write(fp, 'utf-8') 76 | info('writing %s' % complete_path) 77 | 78 | fp.close() 79 | return feed 80 | finally: 81 | locale.setlocale(locale.LC_ALL, old_locale) 82 | 83 | def write_file(self, name, template, context, relative_urls=True, 84 | paginated=None, **kwargs): 85 | """Render the template and write the file. 86 | 87 | :param name: name of the file to output 88 | :param template: template to use to generate the content 89 | :param context: dict to pass to the templates. 90 | :param relative_urls: use relative urls or absolutes ones 91 | :param paginated: dict of article list to paginate - must have the 92 | same length (same list in different orders) 93 | :param **kwargs: additional variables to pass to the templates 94 | """ 95 | 96 | def _write_file(template, localcontext, output_path, name): 97 | """Render the template write the file.""" 98 | old_locale = locale.setlocale(locale.LC_ALL) 99 | locale.setlocale(locale.LC_ALL, 'C') 100 | try: 101 | output = template.render(localcontext) 102 | finally: 103 | locale.setlocale(locale.LC_ALL, old_locale) 104 | filename = os.sep.join((output_path, name)) 105 | try: 106 | os.makedirs(os.path.dirname(filename)) 107 | except Exception: 108 | pass 109 | with open(filename, 'w', encoding='utf-8') as f: 110 | f.write(output) 111 | info(u'writing %s' % filename) 112 | 113 | localcontext = context.copy() 114 | if relative_urls: 115 | localcontext['SITEURL'] = get_relative_path(name) 116 | 117 | localcontext.update(kwargs) 118 | if relative_urls: 119 | self.update_context_contents(name, localcontext) 120 | 121 | # check paginated 122 | paginated = paginated or {} 123 | if paginated: 124 | # pagination needed, init paginators 125 | paginators = {} 126 | for key in paginated.iterkeys(): 127 | object_list = paginated[key] 128 | 129 | if self.settings.get('WITH_PAGINATION'): 130 | paginators[key] = Paginator(object_list, 131 | self.settings.get('DEFAULT_PAGINATION'), 132 | self.settings.get('DEFAULT_ORPHANS')) 133 | else: 134 | paginators[key] = Paginator(object_list, len(object_list), 0) 135 | 136 | # generated pages, and write 137 | for page_num in range(paginators.values()[0].num_pages): 138 | paginated_localcontext = localcontext.copy() 139 | paginated_name = name 140 | for key in paginators.iterkeys(): 141 | paginator = paginators[key] 142 | page = paginator.page(page_num+1) 143 | paginated_localcontext.update({'%s_paginator' % key: paginator, 144 | '%s_page' % key: page}) 145 | if page_num > 0: 146 | ext = '.' + paginated_name.rsplit('.')[-1] 147 | paginated_name = paginated_name.replace(ext, 148 | '%s%s' % (page_num + 1, ext)) 149 | 150 | _write_file(template, paginated_localcontext, self.output_path, 151 | paginated_name) 152 | else: 153 | # no pagination 154 | _write_file(template, localcontext, self.output_path, name) 155 | 156 | def update_context_contents(self, name, context): 157 | """Recursively run the context to find elements (articles, pages, etc) 158 | whose content getter needs to be modified in order to deal with 159 | relative paths. 160 | 161 | :param name: name of the file to output. 162 | :param context: dict that will be passed to the templates, which need to 163 | be updated. 164 | """ 165 | def _update_content(name, input): 166 | """Change all the relatives paths of the input content to relatives 167 | paths suitable fot the ouput content 168 | 169 | :param name: path of the output. 170 | :param input: input resource that will be passed to the templates. 171 | """ 172 | content = input._content 173 | 174 | hrefs = re.compile(r""" 175 | (?P<\s*[^\>]* # match tag with src and href attr 176 | (?:href|src)\s*=\s* 177 | ) 178 | (?P["\']) # require value to be quoted 179 | (?![#?]) # don't match fragment or query URLs 180 | (?![a-z]+:) # don't match protocol URLS 181 | (?P.*?) # the url value 182 | \2""", re.X) 183 | 184 | def replacer(m): 185 | relative_path = m.group('path') 186 | dest_path = os.path.normpath( os.sep.join( (get_relative_path(name), 187 | "static", relative_path) ) ) 188 | return m.group('markup') + m.group('quote') + dest_path + m.group('quote') 189 | 190 | return hrefs.sub(replacer, content) 191 | 192 | if context is None: 193 | return 194 | if hasattr(context, 'values'): 195 | context = context.values() 196 | 197 | for item in context: 198 | # run recursively on iterables 199 | if hasattr(item, '__iter__'): 200 | self.update_context_contents(name, item) 201 | 202 | # if it is a content, patch it 203 | elif hasattr(item, '_content'): 204 | relative_path = get_relative_path(name) 205 | 206 | paths = self.reminder.setdefault(item, []) 207 | if relative_path not in paths: 208 | paths.append(relative_path) 209 | setattr(item, "_get_content", 210 | partial(_update_content, name, item)) 211 | -------------------------------------------------------------------------------- /tools/pelican-import: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | from pelican.utils import slugify 4 | 5 | from codecs import open 6 | import os 7 | import argparse 8 | import time 9 | 10 | 11 | def wp2fields(xml): 12 | """Opens a wordpress XML file, and yield pelican fields""" 13 | from BeautifulSoup import BeautifulStoneSoup 14 | 15 | xmlfile = open(xml, encoding='utf-8').read() 16 | soup = BeautifulStoneSoup(xmlfile) 17 | items = soup.rss.channel.findAll('item') 18 | 19 | for item in items: 20 | if item.fetch('wp:status')[0].contents[0] == "publish": 21 | title = item.title.contents[0] 22 | content = item.fetch('content:encoded')[0].contents[0] 23 | filename = item.fetch('wp:post_name')[0].contents[0] 24 | 25 | raw_date = item.fetch('wp:post_date')[0].contents[0] 26 | date_object = time.strptime(raw_date, "%Y-%m-%d %H:%M:%S") 27 | date = time.strftime("%Y-%m-%d %H:%M", date_object) 28 | 29 | author = item.fetch('dc:creator')[0].contents[0].title() 30 | 31 | categories = [cat.contents[0] for cat in item.fetch(domain='category')] 32 | # caturl = [cat['nicename'] for cat in item.fetch(domain='category')] 33 | 34 | tags = [tag.contents[0].title() for tag in item.fetch(domain='tag', nicename=None)] 35 | 36 | yield (title, content, filename, date, author, categories, tags, "html") 37 | 38 | def dc2fields(file): 39 | """Opens a Dotclear export file, and yield pelican fields""" 40 | in_cat = False 41 | in_post = False 42 | category_list = {} 43 | posts = [] 44 | 45 | with open(file, 'r', encoding='utf-8') as f: 46 | 47 | for line in f: 48 | # remove final \n 49 | line = line[:-1] 50 | 51 | if line.startswith('[category'): 52 | in_cat = True 53 | elif line.startswith('[post'): 54 | in_post = True 55 | elif in_cat: 56 | fields = line.split('","') 57 | if not line: 58 | in_cat = False 59 | else: 60 | # remove 1st and last "" 61 | fields[0] = fields[0][1:] 62 | # fields[-1] = fields[-1][:-1] 63 | category_list[fields[0]]=fields[2] 64 | elif in_post: 65 | if not line: 66 | in_post = False 67 | break 68 | else: 69 | posts.append(line) 70 | 71 | print "%i posts read." % len(posts) 72 | 73 | for post in posts: 74 | fields = post.split('","') 75 | 76 | # post_id = fields[0][1:] 77 | # blog_id = fields[1] 78 | # user_id = fields[2] 79 | cat_id = fields[3] 80 | # post_dt = fields[4] 81 | # post_tz = fields[5] 82 | post_creadt = fields[6] 83 | # post_upddt = fields[7] 84 | # post_password = fields[8] 85 | post_type = fields[9] 86 | post_format = fields[10] 87 | post_url = fields[11] 88 | post_lang = fields[12] 89 | post_title = fields[13] 90 | post_excerpt = fields[14] 91 | post_excerpt_xhtml = fields[15] 92 | post_content = fields[16] 93 | post_content_xhtml = fields[17] 94 | # post_notes = fields[18] 95 | # post_words = fields[19] 96 | # post_status = fields[20] 97 | # post_selected = fields[21] 98 | # post_position = fields[22] 99 | # post_open_comment = fields[23] 100 | # post_open_tb = fields[24] 101 | # nb_comment = fields[25] 102 | # nb_trackback = fields[26] 103 | # post_meta = fields[27] 104 | # redirect_url = fields[28][:-1] 105 | 106 | # remove seconds 107 | post_creadt = ':'.join(post_creadt.split(':')[0:2]) 108 | 109 | author = "" 110 | categories = [] 111 | tags = [] 112 | 113 | if cat_id: 114 | categories = [category_list[id].strip() for id in cat_id.split(',')] 115 | 116 | if post_format == "markdown": 117 | content = post_excerpt + post_content 118 | else: 119 | content = post_excerpt_xhtml + post_content_xhtml 120 | content = content.replace('\\n', '') 121 | post_format = "html" 122 | 123 | yield (post_title, content, post_url, post_creadt, author, categories, tags, post_format) 124 | 125 | 126 | def feed2fields(file): 127 | """Read a feed and yield pelican fields""" 128 | import feedparser 129 | d = feedparser.parse(file) 130 | for entry in d.entries: 131 | date = (time.strftime("%Y-%m-%d %H:%M", entry.updated_parsed) 132 | if hasattr(entry, "updated_parsed") else None) 133 | author = entry.author if hasattr(entry, "author") else None 134 | tags = [e['term'] for e in entry.tags] if hasattr(entry, "tags") else None 135 | 136 | slug = slugify(entry.title) 137 | yield (entry.title, entry.description, slug, date, author, [], tags, "html") 138 | 139 | 140 | def build_header(title, date, author, categories, tags): 141 | """Build a header from a list of fields""" 142 | header = '%s\n%s\n' % (title, '#' * len(title)) 143 | if date: 144 | header += ':date: %s\n' % date 145 | if categories: 146 | header += ':category: %s\n' % ', '.join(categories) 147 | if tags: 148 | header += ':tags: %s\n' % ', '.join(tags) 149 | header += '\n' 150 | return header 151 | 152 | def build_markdown_header(title, date, author, categories, tags): 153 | """Build a header from a list of fields""" 154 | header = 'Title: %s\n' % title 155 | if date: 156 | header += 'Date: %s\n' % date 157 | if categories: 158 | header += 'Category: %s\n' % ', '.join(categories) 159 | if tags: 160 | header += 'Tags: %s\n' % ', '.join(tags) 161 | header += '\n' 162 | return header 163 | 164 | def fields2pelican(fields, output_path, dircat=False): 165 | for title, content, filename, date, author, categories, tags, markup in fields: 166 | if markup == "markdown": 167 | ext = '.md' 168 | header = build_markdown_header(title, date, author, categories, tags) 169 | else: 170 | ext = '.rst' 171 | header = build_header(title, date, author, categories, tags) 172 | 173 | filename = os.path.basename(filename) 174 | 175 | # option to put files in directories with categories names 176 | if dircat and (len(categories) == 1): 177 | catname = categories[0] 178 | out_filename = os.path.join(output_path, catname, filename+'.rst') 179 | if not os.path.isdir(os.path.join(output_path, catname)): 180 | os.mkdir(os.path.join(output_path, catname)) 181 | else: 182 | out_filename = os.path.join(output_path, filename+ext) 183 | 184 | print out_filename 185 | 186 | if markup == "html": 187 | html_filename = os.path.join(output_path, filename+'.html') 188 | 189 | with open(html_filename, 'w', encoding='utf-8') as fp: 190 | fp.write(content) 191 | 192 | os.system('pandoc --normalize --reference-links --from=html --to=rst -o "%s" "%s"' % (out_filename, 193 | html_filename)) 194 | 195 | os.remove(html_filename) 196 | 197 | with open(out_filename, 'r', encoding='utf-8') as fs: 198 | content = fs.read() 199 | 200 | with open(out_filename, 'w', encoding='utf-8') as fs: 201 | fs.write(header + content) 202 | 203 | 204 | def main(input_type, input, output_path, dircat=False): 205 | if input_type == 'wordpress': 206 | fields = wp2fields(input) 207 | elif input_type == 'dotclear': 208 | fields = dc2fields(input) 209 | elif input_type == 'feed': 210 | fields = feed2fields(input) 211 | 212 | fields2pelican(fields, output_path, dircat=dircat) 213 | 214 | 215 | if __name__ == '__main__': 216 | parser = argparse.ArgumentParser( 217 | description="Transform feed, Wordpress or Dotclear files to rst files." 218 | "Be sure to have pandoc installed") 219 | 220 | parser.add_argument(dest='input', help='The input file to read') 221 | parser.add_argument('--wpfile', action='store_true', dest='wpfile', 222 | help='Wordpress XML export') 223 | parser.add_argument('--dotclear', action='store_true', dest='dotclear', 224 | help='Dotclear export') 225 | parser.add_argument('--feed', action='store_true', dest='feed', 226 | help='Feed to parse') 227 | parser.add_argument('-o', '--output', dest='output', default='output', 228 | help='Output path') 229 | parser.add_argument('--dir-cat', action='store_true', dest='dircat', 230 | help='Put files in directories with categories name') 231 | args = parser.parse_args() 232 | 233 | input_type = None 234 | if args.wpfile: 235 | input_type = 'wordpress' 236 | elif args.dotclear: 237 | input_type = 'dotclear' 238 | elif args.feed: 239 | input_type = 'feed' 240 | else: 241 | print "you must provide either --wpfile, --dotclear or --feed options" 242 | exit() 243 | main(input_type, args.input, args.output, dircat=args.dircat) 244 | -------------------------------------------------------------------------------- /tools/pelican-quickstart: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- # 3 | 4 | import os, sys, argparse, string 5 | from pelican import __version__ 6 | 7 | TEMPLATES={ 8 | 'Makefile' : ''' 9 | PELICAN=$pelican 10 | PELICANOPTS=$pelicanopts 11 | 12 | BASEDIR=$$(PWD) 13 | INPUTDIR=$$(BASEDIR)/src 14 | OUTPUTDIR=$$(BASEDIR)/output 15 | CONFFILE=$$(BASEDIR)/pelican.conf.py 16 | 17 | FTP_HOST=$ftp_host 18 | FTP_USER=$ftp_user 19 | FTP_TARGET_DIR=$ftp_target_dir 20 | 21 | SSH_HOST=$ssh_host 22 | SSH_USER=$ssh_user 23 | SSH_TARGET_DIR=$ssh_target_dir 24 | 25 | DROPBOX_DIR=$dropbox_dir 26 | 27 | help: 28 | \t@echo 'Makefile for a pelican Web site ' 29 | \t@echo ' ' 30 | \t@echo 'Usage: ' 31 | \t@echo ' make html (re)generate the web site ' 32 | \t@echo ' make clean remove the generated files ' 33 | \t@echo ' ftp_upload upload the web site using FTP ' 34 | \t@echo ' ssh_upload upload the web site using SSH ' 35 | \t@echo ' dropbox_upload upload the web site using Dropbox ' 36 | \t@echo ' ' 37 | 38 | 39 | html: clean $$(OUTPUTDIR)/index.html 40 | \t@echo 'Done' 41 | 42 | $$(OUTPUTDIR)/%.html: 43 | \t$$(PELICAN) $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) 44 | 45 | clean: 46 | \trm -fr $$(OUTPUTDIR) 47 | \tmkdir $$(OUTPUTDIR) 48 | 49 | dropbox_upload: $$(OUTPUTDIR)/index.html 50 | \tcp -r $$(OUTPUTDIR)/* $$(DROPBOX_DIR) 51 | 52 | ssh_upload: $$(OUTPUTDIR)/index.html 53 | \tscp -r $$(OUTPUTDIR)/* $$(SSH_USER)@$$(SSH_HOST):$$(SSH_TARGET_DIR) 54 | 55 | ftp_upload: $$(OUTPUTDIR)/index.html 56 | \tlftp ftp://$$(FTP_USER)@$$(FTP_HOST) -e "mirror -R $$(OUTPUT_DIR)/* $$(FTP_TARGET_DIR) ; quit" 57 | 58 | github: $$(OUTPUTDIR)/index.html 59 | \tghp-import $$(OUTPUTDIR) 60 | \tgit push origin gh-pages 61 | 62 | .PHONY: html help clean ftp_upload ssh_upload dropbox_upload github 63 | ''', 64 | 65 | 'pelican.conf.py': '''#!/usr/bin/env python 66 | # -*- coding: utf-8 -*- # 67 | 68 | AUTHOR = u"$author" 69 | SITENAME = u"$sitename" 70 | SITEURL = '/' 71 | 72 | TIMEZONE = 'Europe/Paris' 73 | 74 | DEFAULT_LANG='$lang' 75 | 76 | # Blogroll 77 | LINKS = ( 78 | ('Pelican', 'http://docs.notmyidea.org/alexis/pelican/'), 79 | ('Python.org', 'http://python.org'), 80 | ('Jinja2', 'http://jinja.pocoo.org'), 81 | ('You can modify those links in your config file', '#') 82 | ) 83 | 84 | # Social widget 85 | SOCIAL = ( 86 | ('You can add links in your config file', '#'), 87 | ) 88 | 89 | WITH_PAGINATION = $with_pagination 90 | DEFAULT_PAGINATION = $default_pagination 91 | 92 | 93 | ''' 94 | } 95 | 96 | CONF = { 97 | 'pelican' : 'pelican', 98 | 'pelicanopts' : None, 99 | 'basedir': '.', 100 | 'ftp_host': 'localhost', 101 | 'ftp_user': 'anonymous', 102 | 'ftp_target_dir': '/', 103 | 'ssh_host': 'locahost', 104 | 'ssh_user': 'root', 105 | 'ssh_target_dir': '/var/www', 106 | 'dropbox_dir' : '~/Dropbox/Public/', 107 | 'with_pagination' : True, 108 | 'default_pagination' : 7, 109 | 'lang': 'en' 110 | } 111 | 112 | 113 | class _dict(dict): 114 | def __init__(self, *args, **kwargs): 115 | dict.__init__(self, *args, **kwargs) 116 | 117 | def __getitem__(self, i): 118 | return dict.get(self,i,None) 119 | 120 | def has_key(k): 121 | return True 122 | 123 | 124 | def ask(question, answer=str, default=None, l=None): 125 | if answer == str: 126 | r = '' 127 | while True: 128 | if default: 129 | r = raw_input('> {0} [{1}] '.format(question, default)) 130 | else: 131 | r = raw_input('> {0} '.format(question, default)) 132 | 133 | r = r.strip() 134 | 135 | if len(r) <= 0: 136 | if default: 137 | r = default 138 | break 139 | else: 140 | print('You must enter something') 141 | else: 142 | if l and len(r) != l: 143 | print('You must enter a {0} letters long string'.format(l)) 144 | else: 145 | break 146 | 147 | return r 148 | 149 | elif answer == bool: 150 | r = None 151 | while True: 152 | if default is True: 153 | r = raw_input('> {0} (Y/n) '.format(question)) 154 | elif default is False: 155 | r = raw_input('> {0} (y/N) '.format(question)) 156 | else: 157 | r = raw_input('> {0} (y/n) '.format(question)) 158 | 159 | r = r.strip().lower() 160 | 161 | if r in ('y', 'yes'): 162 | r = True 163 | break 164 | elif r in ('n', 'no'): 165 | r = False 166 | break 167 | elif not r: 168 | r = default 169 | break 170 | else: 171 | print("You must answer `yes' or `no'") 172 | return r 173 | elif answer == int: 174 | r = None 175 | while True: 176 | if default: 177 | r = raw_input('> {0} [{1}] '.format(question, default)) 178 | else: 179 | r = raw_input('> {0} '.format(question)) 180 | 181 | r = r.strip() 182 | 183 | if not r: 184 | r = default 185 | break 186 | 187 | try: 188 | r = int(r) 189 | break 190 | except: 191 | print('You must enter an integer') 192 | return r 193 | else: 194 | raise NotImplemented('Arguent `answer` must be str, bool or integer') 195 | 196 | 197 | def main(): 198 | parser = argparse.ArgumentParser(description="A kickstarter for pelican") 199 | parser.add_argument('-p', '--path', default=".", 200 | help="The path to generate the blog into") 201 | parser.add_argument('-t', '--title', default=None, metavar="title", 202 | help='Set the title of the website') 203 | parser.add_argument('-a', '--author', default=None, metavar="author", 204 | help='Set the author name of the website') 205 | parser.add_argument('-l', '--lang', default=None, metavar="lang", 206 | help='Set the default lang of the website') 207 | 208 | args = parser.parse_args() 209 | 210 | 211 | print('''Welcome to pelican-quickstart v{v}. 212 | 213 | This script will help you creating a new Pelican based website. 214 | 215 | Please answer the following questions so this script can generate the files needed by Pelican. 216 | 217 | '''.format(v=__version__)) 218 | 219 | CONF['basedir'] = os.path.abspath(ask('Where do you want to create your new Web site ?', answer=str, default=args.path)) 220 | CONF['sitename'] = ask('How will you call your Web site ?', answer=str, default=args.title) 221 | CONF['author'] = ask('Who will be the author of this Web site ?', answer=str, default=args.author) 222 | CONF['lang'] = ask('What will be the default language of this Web site ?', str, args.lang or CONF['lang'], 2) 223 | 224 | CONF['with_pagination'] = ask('Do you want to enable article pagination ?', bool, CONF['with_pagination']) 225 | 226 | if CONF['with_pagination']: 227 | CONF['default_pagination'] = ask('So how many articles per page do you want ?', int, CONF['default_pagination']) 228 | 229 | mkfile = ask('Do you want to generate a Makefile to easily manage your website ?', bool, True) 230 | 231 | if mkfile: 232 | if ask('Do you want to upload your website using FTP ?', answer=bool, default=False): 233 | CONF['ftp_host'] = ask('What is the hostname of your FTP server ?', str, CONF['ftp_host']) 234 | CONF['ftp_user'] = ask('What is your username on this server ?', str, CONF['ftp_user']) 235 | CONF['ftp_traget_dir'] = ask('Where do you want to put your website on this server ?', str, CONF['ftp_target_dir']) 236 | 237 | if ask('Do you want to upload your website using SSH ?', answer=bool, default=False): 238 | CONF['ssh_host'] = ask('What is the hostname of your SSH server ?', str, CONF['ssh_host']) 239 | CONF['ssh_user'] = ask('What is your username on this server ?', str, CONF['ssh_user']) 240 | CONF['ssh_traget_dir'] = ask('Where do you want to put your website on this server ?', str, CONF['ssh_target_dir']) 241 | 242 | if ask('Do you want to upload your website using Dropbox ?', answer=bool, default=False): 243 | CONF['dropbox_dir'] = ask('Where is your Dropbox directory ?', str, CONF['dropbox_dir']) 244 | 245 | try: 246 | os.makedirs(os.path.join(CONF['basedir'], 'src')) 247 | except OSError, e: 248 | print('Error: {0}'.format(e)) 249 | 250 | try: 251 | os.makedirs(os.path.join(CONF['basedir'], 'output')) 252 | except OSError, e: 253 | print('Error: {0}'.format(e)) 254 | 255 | conf = string.Template(TEMPLATES['pelican.conf.py']) 256 | try: 257 | with open(os.path.join(CONF['basedir'], 'pelican.conf.py'), 'w') as fd: 258 | fd.write(conf.safe_substitute(CONF)) 259 | fd.close() 260 | except OSError, e: 261 | print('Error: {0}'.format(e)) 262 | 263 | if mkfile: 264 | Makefile = string.Template(TEMPLATES['Makefile']) 265 | 266 | try: 267 | with open(os.path.join(CONF['basedir'], 'Makefile'), 'w') as fd: 268 | fd.write(Makefile.safe_substitute(CONF)) 269 | fd.close() 270 | except OSError, e: 271 | print('Error: {0}'.format(e)) 272 | 273 | print('Done. Your new project is available at %s' % CONF['basedir']) 274 | 275 | if __name__ == '__main__': 276 | main() 277 | -------------------------------------------------------------------------------- /pelican/themes/notmyidea/static/css/main.css: -------------------------------------------------------------------------------- 1 | /* 2 | Name: Smashing HTML5 3 | Date: July 2009 4 | Description: Sample layout for HTML5 and CSS3 goodness. 5 | Version: 1.0 6 | Author: Enrique Ramírez 7 | Autor URI: http://enrique-ramirez.com 8 | */ 9 | 10 | /* Imports */ 11 | @import url("reset.css"); 12 | @import url("pygment.css"); 13 | @import url(http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz&subset=latin); 14 | 15 | /***** Global *****/ 16 | /* Body */ 17 | body { 18 | background: #F5F4EF url('../images/bg.png'); 19 | color: #000305; 20 | font-size: 87.5%; /* Base font size: 14px */ 21 | font-family: 'Trebuchet MS', Trebuchet, 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; 22 | line-height: 1.429; 23 | margin: 0; 24 | padding: 0; 25 | text-align: left; 26 | } 27 | 28 | 29 | 30 | /* Headings */ 31 | h1 {font-size: 2em } 32 | h2 {font-size: 1.571em} /* 22px */ 33 | h3 {font-size: 1.429em} /* 20px */ 34 | h4 {font-size: 1.286em} /* 18px */ 35 | h5 {font-size: 1.143em} /* 16px */ 36 | h6 {font-size: 1em} /* 14px */ 37 | 38 | h1, h2, h3, h4, h5, h6 { 39 | font-weight: 400; 40 | line-height: 1.1; 41 | margin-bottom: .8em; 42 | font-family: 'Yanone Kaffeesatz', arial, serif; 43 | } 44 | 45 | h3, h4, h5, h6 { margin-top: .8em; } 46 | 47 | hr { border: 2px solid #EEEEEE; } 48 | 49 | /* Anchors */ 50 | a {outline: 0;} 51 | a img {border: 0px; text-decoration: none;} 52 | a:link, a:visited { 53 | color: #C74350; 54 | padding: 0 1px; 55 | text-decoration: underline; 56 | } 57 | a:hover, a:active { 58 | background-color: #C74350; 59 | color: #fff; 60 | text-decoration: none; 61 | text-shadow: 1px 1px 1px #333; 62 | } 63 | 64 | h1 a:hover { 65 | background-color: inherit 66 | } 67 | 68 | /* Paragraphs */ 69 | p {margin-bottom: 1.143em;} 70 | 71 | strong, b {font-weight: bold;} 72 | em, i {font-style: italic;} 73 | 74 | ::-moz-selection {background: #F6CF74; color: #fff;} 75 | ::selection {background: #F6CF74; color: #fff;} 76 | 77 | /* Lists */ 78 | ul { 79 | list-style: outside disc; 80 | margin: 1em 0 1.5em 1.5em; 81 | } 82 | 83 | ol { 84 | list-style: outside decimal; 85 | margin: 1em 0 1.5em 1.5em; 86 | } 87 | 88 | .post-info { 89 | float:right; 90 | margin:10px; 91 | padding:5px; 92 | } 93 | 94 | .post-info p{ 95 | margin-bottom: 1px; 96 | } 97 | 98 | .readmore { float: right } 99 | 100 | dl {margin: 0 0 1.5em 0;} 101 | dt {font-weight: bold;} 102 | dd {margin-left: 1.5em;} 103 | 104 | pre{background-color: #000; padding: 10px; color: #fff; margin: 10px; overflow: auto;} 105 | 106 | /* Quotes */ 107 | blockquote { 108 | margin: 20px; 109 | font-style: italic; 110 | } 111 | cite {} 112 | 113 | q {} 114 | 115 | /* Tables */ 116 | table {margin: .5em auto 1.5em auto; width: 98%;} 117 | 118 | /* Thead */ 119 | thead th {padding: .5em .4em; text-align: left;} 120 | thead td {} 121 | 122 | /* Tbody */ 123 | tbody td {padding: .5em .4em;} 124 | tbody th {} 125 | 126 | tbody .alt td {} 127 | tbody .alt th {} 128 | 129 | /* Tfoot */ 130 | tfoot th {} 131 | tfoot td {} 132 | 133 | /* HTML5 tags */ 134 | header, section, footer, 135 | aside, nav, article, figure { 136 | display: block; 137 | } 138 | 139 | /***** Layout *****/ 140 | .body {clear: both; margin: 0 auto; width: 800px;} 141 | img.right figure.right {float: right; margin: 0 0 2em 2em;} 142 | img.left, figure.left {float: right; margin: 0 0 2em 2em;} 143 | 144 | /* 145 | Header 146 | *****************/ 147 | #banner { 148 | margin: 0 auto; 149 | padding: 2.5em 0 0 0; 150 | } 151 | 152 | /* Banner */ 153 | #banner h1 {font-size: 3.571em; line-height: 0;} 154 | #banner h1 a:link, #banner h1 a:visited { 155 | color: #000305; 156 | display: block; 157 | font-weight: bold; 158 | margin: 0 0 .6em .2em; 159 | text-decoration: none; 160 | width: 427px; 161 | } 162 | #banner h1 a:hover, #banner h1 a:active { 163 | background: none; 164 | color: #C74350; 165 | text-shadow: none; 166 | } 167 | 168 | #banner h1 strong {font-size: 0.36em; font-weight: normal;} 169 | 170 | /* Main Nav */ 171 | #banner nav { 172 | background: #000305; 173 | font-size: 1.143em; 174 | height: 40px; 175 | line-height: 30px; 176 | margin: 0 auto 2em auto; 177 | padding: 0; 178 | text-align: center; 179 | width: 800px; 180 | 181 | border-radius: 5px; 182 | -moz-border-radius: 5px; 183 | -webkit-border-radius: 5px; 184 | } 185 | 186 | #banner nav ul {list-style: none; margin: 0 auto; width: 800px;} 187 | #banner nav li {float: left; display: inline; margin: 0;} 188 | 189 | #banner nav a:link, #banner nav a:visited { 190 | color: #fff; 191 | display: inline-block; 192 | height: 30px; 193 | padding: 5px 1.5em; 194 | text-decoration: none; 195 | } 196 | #banner nav a:hover, #banner nav a:active, 197 | #banner nav .active a:link, #banner nav .active a:visited { 198 | background: #C74451; 199 | color: #fff; 200 | text-shadow: none !important; 201 | } 202 | 203 | #banner nav li:first-child a { 204 | border-top-left-radius: 5px; 205 | -moz-border-radius-topleft: 5px; 206 | -webkit-border-top-left-radius: 5px; 207 | 208 | border-bottom-left-radius: 5px; 209 | -moz-border-radius-bottomleft: 5px; 210 | -webkit-border-bottom-left-radius: 5px; 211 | } 212 | 213 | /* 214 | Featured 215 | *****************/ 216 | #featured { 217 | background: #fff; 218 | margin-bottom: 2em; 219 | overflow: hidden; 220 | padding: 20px; 221 | width: 760px; 222 | 223 | border-radius: 10px; 224 | -moz-border-radius: 10px; 225 | -webkit-border-radius: 10px; 226 | } 227 | 228 | #featured figure { 229 | border: 2px solid #eee; 230 | float: right; 231 | margin: 0.786em 2em 0 5em; 232 | width: 248px; 233 | } 234 | #featured figure img {display: block; float: right;} 235 | 236 | #featured h2 {color: #C74451; font-size: 1.714em; margin-bottom: 0.333em;} 237 | #featured h3 {font-size: 1.429em; margin-bottom: .5em;} 238 | 239 | #featured h3 a:link, #featured h3 a:visited {color: #000305; text-decoration: none;} 240 | #featured h3 a:hover, #featured h3 a:active {color: #fff;} 241 | 242 | /* 243 | Body 244 | *****************/ 245 | #content { 246 | background: #fff; 247 | margin-bottom: 2em; 248 | overflow: hidden; 249 | padding: 20px 20px; 250 | width: 760px; 251 | 252 | border-radius: 10px; 253 | -moz-border-radius: 10px; 254 | -webkit-border-radius: 10px; 255 | } 256 | 257 | /* 258 | Extras 259 | *****************/ 260 | #extras {margin: 0 auto 3em auto; overflow: hidden;} 261 | 262 | #extras ul {list-style: none; margin: 0;} 263 | #extras li {border-bottom: 1px solid #fff;} 264 | #extras h2 { 265 | color: #C74350; 266 | font-size: 1.429em; 267 | margin-bottom: .25em; 268 | padding: 0 3px; 269 | } 270 | 271 | #extras a:link, #extras a:visited { 272 | color: #444; 273 | display: block; 274 | border-bottom: 1px solid #F4E3E3; 275 | text-decoration: none; 276 | padding: .3em .25em; 277 | } 278 | 279 | #extras a:hover, #extras a:active {color: #fff;} 280 | 281 | /* Blogroll */ 282 | #extras .blogroll { 283 | float: left; 284 | width: 615px; 285 | } 286 | 287 | #extras .blogroll li {float: left; margin: 0 20px 0 0; width: 185px;} 288 | 289 | /* Social */ 290 | #extras .social { 291 | float: right; 292 | width: 175px; 293 | } 294 | 295 | #extras div[class='social'] a { 296 | background-repeat: no-repeat; 297 | background-position: 3px 6px; 298 | padding-left: 25px; 299 | } 300 | 301 | /* Icons */ 302 | .social a[href*='delicious.com'] {background-image: url('../images/icons/delicious.png');} 303 | .social a[href*='digg.com'] {background-image: url('../images/icons/digg.png');} 304 | .social a[href*='facebook.com'] {background-image: url('../images/icons/facebook.png');} 305 | .social a[href*='last.fm'], .social a[href*='lastfm.'] {background-image: url('../images/icons/lastfm.png');} 306 | .social a[href*='atom.xml'] {background-image: url('../images/icons/rss.png');} 307 | .social a[href*='twitter.com'] {background-image: url('../images/icons/twitter.png');} 308 | .social a[href*='linkedin.com'] {background-image: url('../images/icons/linkedin.png');} 309 | 310 | /* 311 | About 312 | *****************/ 313 | #about { 314 | background: #fff; 315 | font-style: normal; 316 | margin-bottom: 2em; 317 | overflow: hidden; 318 | padding: 20px; 319 | text-align: left; 320 | width: 760px; 321 | 322 | border-radius: 10px; 323 | -moz-border-radius: 10px; 324 | -webkit-border-radius: 10px; 325 | } 326 | 327 | #about .primary {float: left; width: 165px;} 328 | #about .primary strong {color: #C64350; display: block; font-size: 1.286em;} 329 | #about .photo {float: left; margin: 5px 20px;} 330 | 331 | #about .url:link, #about .url:visited {text-decoration: none;} 332 | 333 | #about .bio {float: right; width: 500px;} 334 | 335 | /* 336 | Footer 337 | *****************/ 338 | #contentinfo {padding-bottom: 2em; text-align: right;} 339 | 340 | /***** Sections *****/ 341 | /* Blog */ 342 | .hentry { 343 | display: block; 344 | clear: both; 345 | border-bottom: 1px solid #eee; 346 | padding: 1.5em 0; 347 | } 348 | li:last-child .hentry, #content > .hentry {border: 0; margin: 0;} 349 | #content > .hentry {padding: 1em 0;} 350 | .hentry img{display : none ;} 351 | .entry-title {font-size: 3em; margin-bottom: 10px; margin-top: 0;} 352 | .entry-title a:link, .entry-title a:visited {text-decoration: none; color: #333;} 353 | .entry-title a:visited {background-color: #fff;} 354 | 355 | .hentry .post-info * {font-style: normal;} 356 | 357 | /* Content */ 358 | .hentry footer {margin-bottom: 2em;} 359 | .hentry footer address {display: inline;} 360 | #posts-list footer address {display: block;} 361 | 362 | /* Blog Index */ 363 | #posts-list {list-style: none; margin: 0;} 364 | #posts-list .hentry {padding-left: 10px; position: relative;} 365 | 366 | #posts-list footer { 367 | left: 10px; 368 | position: relative; 369 | float: left; 370 | top: 0.5em; 371 | width: 190px; 372 | } 373 | 374 | /* About the Author */ 375 | #about-author { 376 | background: #f9f9f9; 377 | clear: both; 378 | font-style: normal; 379 | margin: 2em 0; 380 | padding: 10px 20px 15px 20px; 381 | 382 | border-radius: 5px; 383 | -moz-border-radius: 5px; 384 | -webkit-border-radius: 5px; 385 | } 386 | 387 | #about-author strong { 388 | color: #C64350; 389 | clear: both; 390 | display: block; 391 | font-size: 1.429em; 392 | } 393 | 394 | #about-author .photo {border: 1px solid #ddd; float: left; margin: 5px 1em 0 0;} 395 | 396 | /* Comments */ 397 | #comments-list {list-style: none; margin: 0 1em;} 398 | #comments-list blockquote { 399 | background: #f8f8f8; 400 | clear: both; 401 | font-style: normal; 402 | margin: 0; 403 | padding: 15px 20px; 404 | 405 | border-radius: 5px; 406 | -moz-border-radius: 5px; 407 | -webkit-border-radius: 5px; 408 | } 409 | #comments-list footer {color: #888; padding: .5em 1em 0 0; text-align: right;} 410 | 411 | #comments-list li:nth-child(2n) blockquote {background: #F5f5f5;} 412 | 413 | /* Add a Comment */ 414 | #add-comment label {clear: left; float: left; text-align: left; width: 150px;} 415 | #add-comment input[type='text'], 416 | #add-comment input[type='email'], 417 | #add-comment input[type='url'] {float: left; width: 200px;} 418 | 419 | #add-comment textarea {float: left; height: 150px; width: 495px;} 420 | 421 | #add-comment p.req {clear: both; margin: 0 .5em 1em 0; text-align: right;} 422 | 423 | #add-comment input[type='submit'] {float: right; margin: 0 .5em;} 424 | #add-comment * {margin-bottom: .5em;} 425 | -------------------------------------------------------------------------------- /docs/themes.rst: -------------------------------------------------------------------------------- 1 | .. _theming-pelican: 2 | 3 | How to create themes for pelican 4 | ################################ 5 | 6 | Pelican uses the great `jinja2 `_ templating engine to 7 | generate it's HTML output. The jinja2 syntax is really simple. If you want to 8 | create your own theme, feel free to take inspiration from the "simple" theme, 9 | which is available `here 10 | `_ 11 | 12 | Structure 13 | ========= 14 | 15 | To make your own theme, you must follow the following structure:: 16 | 17 | ├── static 18 | │   ├── css 19 | │   └── images 20 | └── templates 21 | ├── archives.html // to display archives 22 | ├── article.html // processed for each article 23 | ├── author.html // processed for each author 24 | ├── authors.html // must list all the authors 25 | ├── categories.html // must list all the categories 26 | ├── category.html // processed for each category 27 | ├── index.html // the index. List all the articles 28 | ├── page.html // processed for each page 29 | ├── tag.html // processed for each tag 30 | └── tags.html // must list all the tags. Can be a tag cloud. 31 | 32 | * `static` contains all the static content. It will be copied on the output 33 | `theme/static` folder then. I've put the css and image folders, but they are 34 | just examples. Put what you need here. 35 | 36 | * `templates` contains all the templates that will be used to generate the content. 37 | I've just put the mandatory templates here, you can define your own if it helps 38 | you to organize yourself while doing the theme. 39 | 40 | Templates and variables 41 | ======================= 42 | 43 | It's using a simple syntax, that you can embbed into your html pages. 44 | This document describes which templates should exist on a theme, and which 45 | variables will be passed to each template, while generating it. 46 | 47 | All templates will receive the variables defined in your settings file, if they 48 | are in caps. You can access them directly. 49 | 50 | Common variables 51 | ---------------- 52 | 53 | All of those settings will be given to all templates. 54 | 55 | ============= =================================================== 56 | Variable Description 57 | ============= =================================================== 58 | articles That's the list of articles, ordered desc. by date 59 | all the elements are `Article` objects, so you can 60 | access their properties (e.g. title, summary, author 61 | etc.). 62 | dates The same list of article, but ordered by date, 63 | ascending. 64 | tags A dict containing each tags (keys), and the list of 65 | relative articles. 66 | categories A dict containing each category (keys), and the 67 | list of relative articles. 68 | pages The list of pages. 69 | ============= =================================================== 70 | 71 | index.html 72 | ---------- 73 | 74 | Home page of your blog, will finally remain at output/index.html. 75 | 76 | If pagination is active, next pages will remain at output/index`n`.html. 77 | 78 | =================== =================================================== 79 | Variable Description 80 | =================== =================================================== 81 | articles_paginator A paginator object of article list. 82 | articles_page The current page of articles. 83 | dates_paginator A paginator object of article list, ordered by date, 84 | ascending. 85 | dates_page The current page of articles, ordered by date, 86 | ascending. 87 | page_name 'index'. Useful for pagination links. 88 | =================== =================================================== 89 | 90 | author.html 91 | ------------- 92 | 93 | This template will be processed for each of the existing authors, and will 94 | finally remain at output/author/`author_name`.html. 95 | 96 | If pagination is active, next pages will remain at 97 | output/author/`author_name``n`.html. 98 | 99 | =================== =================================================== 100 | Variable Description 101 | =================== =================================================== 102 | author The name of the author being processed. 103 | articles Articles of this author. 104 | dates Articles of this author, but ordered by date, 105 | ascending. 106 | articles_paginator A paginator object of article list. 107 | articles_page The current page of articles. 108 | dates_paginator A paginator object of article list, ordered by date, 109 | ascending. 110 | dates_page The current page of articles, ordered by date, 111 | ascending. 112 | page_name 'author/`author_name`'. Useful for pagination 113 | links. 114 | =================== =================================================== 115 | 116 | category.html 117 | ------------- 118 | 119 | This template will be processed for each of the existing categories, and will 120 | finally remain at output/category/`category_name`.html. 121 | 122 | If pagination is active, next pages will remain at 123 | output/category/`category_name``n`.html. 124 | 125 | =================== =================================================== 126 | Variable Description 127 | =================== =================================================== 128 | category The name of the category being processed. 129 | articles Articles of this category. 130 | dates Articles of this category, but ordered by date, 131 | ascending. 132 | articles_paginator A paginator object of article list. 133 | articles_page The current page of articles. 134 | dates_paginator A paginator object of article list, ordered by date, 135 | ascending. 136 | dates_page The current page of articles, ordered by date, 137 | ascending. 138 | page_name 'category/`category_name`'. Useful for pagination 139 | links. 140 | =================== =================================================== 141 | 142 | article.html 143 | ------------- 144 | 145 | This template will be processed for each article. .html files will be output 146 | in output/`article_name`.html. Here are the specific variables it gets. 147 | 148 | ============= =================================================== 149 | Variable Description 150 | ============= =================================================== 151 | article The article object to be displayed. 152 | category The name of the category of the current article. 153 | ============= =================================================== 154 | 155 | page.html 156 | --------- 157 | 158 | For each page, this template will be processed. It will create .html files in 159 | output/`page_name`.html. 160 | 161 | ============= =================================================== 162 | Variable Description 163 | ============= =================================================== 164 | page The page object to be displayed. You can access to 165 | its title, slug and content. 166 | ============= =================================================== 167 | 168 | tag.html 169 | -------- 170 | 171 | For each tag, this template will be processed. It will create .html files in 172 | output/tag/`tag_name`.html. 173 | 174 | If pagination is active, next pages will remain at 175 | output/tag/`tag_name``n`.html. 176 | 177 | =================== =================================================== 178 | Variable Description 179 | =================== =================================================== 180 | tag The name of the tag being processed. 181 | articles Articles related to this tag. 182 | dates Articles related to this tag, but ordered by date, 183 | ascending. 184 | articles_paginator A paginator object of article list. 185 | articles_page The current page of articles. 186 | dates_paginator A paginator object of article list, ordered by date, 187 | ascending. 188 | dates_page The current page of articles, ordered by date, 189 | ascending. 190 | page_name 'tag/`tag_name`'. Useful for pagination links. 191 | =================== =================================================== 192 | 193 | Inheritance 194 | =========== 195 | 196 | Since version 3, pelican supports inheritance from the ``simple`` theme, so you can reuse the templates of the ``simple`` theme in your own themes: 197 | 198 | 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 of the ``simple`` theme is right for you, you don't have to rewrite it from scratch. 199 | 200 | You can also extend templates of the ``simple`` themes in your own themes by using the ``{% extends %}`` directive as in the following example: 201 | 202 | .. code-block:: html+jinja 203 | 204 | {% extends "!simple/index.html" %} 205 | 206 | {% extends "index.html" %} 207 | 208 | 209 | Example 210 | ------- 211 | 212 | With this system, it is possible to create a theme with just two files. 213 | 214 | base.html 215 | """"""""" 216 | 217 | The first file is the ``templates/base.html`` template: 218 | 219 | .. code-block:: html+jinja 220 | 221 | {% extends "!simple/base.html" %} 222 | 223 | {% block head %} 224 | {{ super() }} 225 | 226 | {% endblock %} 227 | 228 | 229 | 1. On the first line, we extend the ``base.html`` template of the ``simple`` theme, so we don't have to rewrite the entire file. 230 | 2. On the third line, we open the ``head`` block which has already been defined in the ``simple`` theme 231 | 3. On the fourth line, the function ``super()`` keeps the content previously inserted in the ``head`` block. 232 | 4. On the fifth line, we append a stylesheet to the page 233 | 5. On the last line, we close the ``head`` block. 234 | 235 | This file will be extended by all the other templates, so the stylesheet will be linked from all pages. 236 | 237 | style.css 238 | """"""""" 239 | 240 | The second file is the ``static/css/style.css`` CSS stylesheet: 241 | 242 | .. code-block:: css 243 | 244 | body { 245 | font-family : monospace ; 246 | font-size : 100% ; 247 | background-color : white ; 248 | color : #111 ; 249 | width : 80% ; 250 | min-width : 400px ; 251 | min-height : 200px ; 252 | padding : 1em ; 253 | margin : 5% 10% ; 254 | border : thin solid gray ; 255 | border-radius : 5px ; 256 | display : block ; 257 | } 258 | 259 | a:link { color : blue ; text-decoration : none ; } 260 | a:hover { color : blue ; text-decoration : underline ; } 261 | a:visited { color : blue ; } 262 | 263 | h1 a { color : inherit !important } 264 | h2 a { color : inherit !important } 265 | h3 a { color : inherit !important } 266 | h4 a { color : inherit !important } 267 | h5 a { color : inherit !important } 268 | h6 a { color : inherit !important } 269 | 270 | pre { 271 | margin : 2em 1em 2em 4em ; 272 | } 273 | 274 | #menu li { 275 | display : inline ; 276 | } 277 | 278 | #post-list { 279 | margin-bottom : 1em ; 280 | margin-top : 1em ; 281 | } 282 | 283 | Download 284 | """""""" 285 | 286 | You can download this example theme :download:`here <_static/theme-basic.zip>`. 287 | --------------------------------------------------------------------------------