diff --git a/.gitignore b/.gitignore index 9274ba2d..9f9404ef 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ build dist tags .tox +.coverage +htmlcov diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index 373892cf..00000000 --- a/CHANGELOG +++ /dev/null @@ -1,108 +0,0 @@ -3.0 - XX/XX/XXXX - -* Refactored the way URL are handled. -* Improved the english documentation -* Fixed packaging using setuptools entrypoints -* Added typogrify support -* Added a way to disable feed generation -* Added support for DIRECT_TEMPLATES -* Allow multiple extensions for content files -* Added less support -* Improved the import script -* Fixed a bunch of bugs :-) -* Added functional tests -* Rsync support in the generated Makefile -* Improved feed support (easily pluggable with feedburner for instance) - -2.8 - -* dotclear importer -* Allow the usage of markdown extensions -* Themes are now easily extensible -* Don't output pagination information if there is only one page. -* Add a page per author, with all their articles -* Improved the test suite -* Made the themes more easy to extend -* Removed Skribit support -* Added a "pelican-quickstart" script -* Fixed timezone-related issues -* Add some scripts for windows support -* Date can be specified in seconds -* Never fail when generating posts (skip and continue) -* Allow the use of future dates -* Support having different timezones per languages. -* Enhanced the documentation - -2.7 - -* Uses logging rather than echoing to stdout -* Support custom jinja filters -* Compatibility with python 2.5 -* Add a theme manager -* Packaged for debian -* Add draft support - -2.6 - -* changes in the output directory structure -* makes templates easier to work with / create -* Add RSS support (was only atom previously) -* Add tag support for the feeds -* Enhance the documentation -* Add another theme (brownstone) -* Add translations -* Add a way to use "cleaner urls" with a rewrite url module (or equivalent) -* Add a tag cloud -* Add an autoreloading feature: the blog is automatically regenerated each time a modification is detected -* Translate the documentation in french -* import a blog from an rss feed -* Pagination support -* Add skribit support - -2.5 - -* import from wordpress -* add some new themes (martyalchin / wide-notmyidea) -* first bug report ! -* linkedin support -* added a FAQ -* google analytics support -* twitter support -* use relative urls not static ones - -2.4 - -* minor themes changes -* add disqus support (so we have comments) -* another code refactoring -* add config settings about pages -* blog entries can also be generated in pdf - -2.3 - -* markdown support - -2.2 - -* Prettify output -* Manages static pages as well - -2.1 - -* Put the notmyidea theme by default - -2.0 - -* Refactoring to be more extensible -* Change into the setting variables - -1.2 - -* Add a debug option -* Add feeds per category -* Use filsystem to get dates if no metadata provided -* Add pygment support - -1.1: - -* first working version diff --git a/MANIFEST.in b/MANIFEST.in index 13ea58a1..bec6d1a3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,6 @@ include *.rst global-include *.py recursive-include pelican *.html *.css *png *.in -include LICENSE +include LICENSE THANKS +recursive-include tests * +recursive-exclude tests *.pyc diff --git a/README.rst b/README.rst index 5012bb9c..018f73ba 100644 --- a/README.rst +++ b/README.rst @@ -1,12 +1,14 @@ Pelican -####### +======= -.. image:: https://secure.travis-ci.org/ametaireau/pelican.png?branch=master +.. image:: https://secure.travis-ci.org/getpelican/pelican.png?branch=master + :target: http://travis-ci.org/#!/getpelican/pelican + :alt: Travis-ci: continuous integration status. -Pelican is a simple weblog generator, written in `Python `_. +Pelican is a static site generator, written in Python_. -* Write your weblog entries directly with your editor of choice (vim!) - in `reStructuredText `_ or `Markdown `_ +* Write your weblog entries directly with your editor of choice (vim!) + in reStructuredText_ or Markdown_ * Includes a simple CLI tool to (re)generate the weblog * Easy to interface with DVCSes and web hooks * Completely static output is easy to host anywhere @@ -20,40 +22,49 @@ Pelican currently supports: * Comments, via an external service (Disqus). (Please note that while useful, Disqus is an external service, and thus the comment data will be somewhat outside of your control and potentially subject to data loss.) -* Theming support (themes are created using `jinja2 `_) +* Theming support (themes are created using Jinja2_ templates) * PDF generation of the articles/pages (optional) * Publication of articles in multiple languages * Atom/RSS feeds * Code syntax highlighting +* Compilation of `LESS CSS`_ (optional) * Import from WordPress, Dotclear, or RSS feeds * Integration with external tools: Twitter, Google Analytics, etc. (optional) -Have a look at `the documentation `_ for -more information. +Have a look at the `Pelican documentation`_ for more information. Why the name "Pelican"? ------------------------- +----------------------- -Heh, you didn't notice? "Pelican" is an anagram for « Calepin » ;) +"Pelican" is an anagram for *calepin*, which means "notebook" in French. ;) Source code ----------- -You can access the source code via git at: https://github.com/ametaireau/pelican +You can access the source code at: https://github.com/getpelican/pelican -If you feel hackish, have a look at the explanation of `Pelican's internals -`_. +If you feel hackish, have a look at the explanation of `Pelican's internals`_. Feedback / Contact us --------------------- -If you want to see new features in Pelican, don't hesitate to offer suggestions, -clone the repository, etc. There are many ways to `contribute -`_. That's open source, dude! +If you want to see new features in Pelican, don't hesitate to offer +suggestions, clone the repository, etc. There are many ways to contribute_. +That's open source, dude! -Contact me at "alexis at notmyidea dot org" for any request/feedback! You can -also join the team at `#pelican on irc.freenode.org -`_ -(or if you don't have any IRC client, use `the webchat -`_) -for quick feedback. +Send a message to "authors at getpelican dot com" with any requests/feedback! You +can also join the team at `#pelican on Freenode`_ (or if you don't have an IRC +client handy, use the webchat_ for quick feedback. + +.. Links + +.. _Python: http://www.python.org/ +.. _reStructuredText: http://docutils.sourceforge.net/rst.html +.. _Markdown: http://daringfireball.net/projects/markdown/ +.. _Jinja2: http://jinja.pocoo.org/ +.. _`LESS CSS`: http://lesscss.org/ +.. _`Pelican documentation`: http://docs.getpelican.com/latest/ +.. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html +.. _`#pelican on Freenode`: irc://irc.freenode.net/pelican +.. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4 +.. _contribute: http://docs.getpelican.com/en/latest/contribute.html diff --git a/dev_requirements.txt b/dev_requirements.txt index ef1dbf31..acf01773 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -1,10 +1,8 @@ -Jinja2 -Pygments -docutils -feedgenerator +# Tests unittest2 -pytz mock +# Optional Packages Markdown -blinker BeautifulSoup +typogrify +webassets \ No newline at end of file diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 00000000..6cea6d93 --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1,129 @@ +Release history +############### + +3.1 (XXXX-XX-XX) +================ + +* Improve handling of links to intra-site resources + +3.0 (2012-08-08) +================== + +* Refactored the way URLs are handled +* Improved the English documentation +* Fixed packaging using ``setuptools`` entrypoints +* Added ``typogrify`` support +* Added a way to disable feed generation +* Added support for ``DIRECT_TEMPLATES`` +* Allow multiple extensions for content files +* Added LESS support +* Improved the import script +* Added functional tests +* Rsync support in the generated Makefile +* Improved feed support (easily pluggable with Feedburner for instance) +* Added support for ``abbr`` in reST +* Fixed a bunch of bugs :-) + +2.8 (2012-02-28) +================== + +* Dotclear importer +* Allow the usage of Markdown extensions +* Themes are now easily extensible +* Don't output pagination information if there is only one page +* Add a page per author, with all their articles +* Improved the test suite +* Made the themes easier to extend +* Removed Skribit support +* Added a ``pelican-quickstart`` script +* Fixed timezone-related issues +* Added some scripts for Windows support +* Date can be specified in seconds +* Never fail when generating posts (skip and continue) +* Allow the use of future dates +* Support having different timezones per language +* Enhanced the documentation + +2.7 (2011-06-11) +================== + +* Use ``logging`` rather than echoing to stdout +* Support custom Jinja filters +* Compatibility with Python 2.5 +* Added a theme manager +* Packaged for Debian +* Added draft support + +2.6 (2011-03-08) +================== + +* Changes in the output directory structure +* Makes templates easier to work with / create +* Added RSS support (was Atom-only) +* Added tag support for the feeds +* Enhance the documentation +* Added another theme (brownstone) +* Added translations +* Added a way to use cleaner URLs with a rewrite url module (or equivalent) +* Added a tag cloud +* Added an autoreloading feature: the blog is automatically regenerated each time a modification is detected +* Translate the documentation into French +* Import a blog from an RSS feed +* Pagination support +* Added Skribit support + +2.5 (2010-11-20) +================== + +* Import from Wordpress +* Added some new themes (martyalchin / wide-notmyidea) +* First bug report! +* Linkedin support +* Added a FAQ +* Google Analytics support +* Twitter support +* Use relative URLs, not static ones + +2.4 (2010-11-06) +================ + +* Minor themes changes +* Add Disqus support (so we have comments) +* Another code refactoring +* Added config settings about pages +* Blog entries can also be generated in PDF + +2.3 (2010-10-31) +================ + +* Markdown support + +2.2 (2010-10-30) +================ + +* Prettify output +* Manages static pages as well + +2.1 (2010-10-30) +================ + +* Make notmyidea the default theme + +2.0 (2010-10-30) +================ + +* Refactoring to be more extensible +* Change into the setting variables + +1.2 (2010-09-28) +================ + +* Added a debug option +* Added per-category feeds +* Use filesystem to get dates if no metadata is provided +* Add Pygments support + +1.1 (2010-08-19) +================ + +* First working version diff --git a/docs/conf.py b/docs/conf.py index ac2d67ee..2a11fe3e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -24,7 +24,7 @@ html_theme = 'pelican' html_theme_options = { 'nosidebar': True, 'index_logo': 'pelican.png', - 'github_fork': 'ametaireau/pelican', + 'github_fork': 'getpelican/pelican', } html_static_path = ['_static'] diff --git a/docs/contribute.rst b/docs/contribute.rst index fcf8d5c0..0820d5c3 100644 --- a/docs/contribute.rst +++ b/docs/contribute.rst @@ -3,14 +3,17 @@ How to contribute? There are many ways to contribute to Pelican. You can enhance the documentation, add missing features, and fix bugs (or just report them). -Don't hesitate to fork and make a pull request on GitHub. +Don't hesitate to fork and make a pull request on GitHub. When doing so, please +create a new feature branch as opposed to making your commits in the master +branch. Setting up the development environment ====================================== You're free to set up your development environment any way you like. Here is a -way using virtualenv and virtualenvwrapper. If you don't have them, you can -install these packages via:: +way using the `virtualenv `_ and `virtualenvwrapper +`_ tools. If you don't +have them, you can install these both of these packages via:: $ pip install virtualenvwrapper @@ -20,30 +23,47 @@ different projects. To create a virtual environment, use the following syntax:: - $ mkvirtualenv pelican + $ mkvirtualenv pelican -To manually install the dependencies:: +To clone the Pelican source:: + $ git clone https://github.com/getpelican/pelican.git src/pelican + +To install the development dependencies:: + + $ cd src/pelican $ pip install -r dev_requirements.txt + +To install Pelican and its dependencies:: + $ python setup.py develop Running the test suite ====================== Each time you add a feature, there are two things to do regarding tests: -checking that the existing tests pass, and adding tests for your new feature -or for the bug you're fixing. +checking that the existing tests pass, and adding tests for the new feature +or bugfix. The tests live in "pelican/tests" and you can run them using the "discover" feature of unittest2:: $ unit2 discover +If you have made changes that affect the output of a Pelican-generated weblog, +then you should update the output used by functional tests. +To do so, you can use the following two commands:: + + $ LC_ALL="C" pelican -o tests/output/custom/ -s samples/pelican.conf.py \ + samples/content/ + $ LC_ALL="C" USER="Dummy Author" pelican -o tests/output/basic/ samples/content/ + Coding standards ================ -Try to respect what is described in the PEP8 -(http://www.python.org/dev/peps/pep-0008/) when providing patches. This can be -eased by the pep8 tool (http://pypi.python.org/pypi/pep8) or by Flake8, which -will give you some other cool hints about what's good or wrong -(http://pypi.python.org/pypi/flake8/) +Try to respect what is described in the `PEP8 specification +`_ when providing patches. This can be +eased via the `pep8 `_ or `flake8 +`_ tools, the latter of which in +particular will give you some useful hints about ways in which the +code/formatting can be improved. diff --git a/docs/faq.rst b/docs/faq.rst index b3dbca87..e76bea6a 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -3,19 +3,47 @@ Frequently Asked Questions (FAQ) Here is a summary of the frequently asked questions for Pelican. +What's the best way to communicate a problem, question, or suggestion? +====================================================================== + +If you have a problem, question, or suggestion, please start by striking up a +conversation on `#pelican on Freenode `_. +Those who don't have an IRC client handy can jump in immediately via +`IRC webchat `_. Because +of differing time zones, you may not get an immediate response to your question, +but please be patient and stay logged into IRC — someone will almost always +respond. + +If you are unable to resolve your issue or if you have a feature request, please +refer to the `issue tracker `_. + +How can I help? +================ + +There are several ways to help out. First, you can use Pelican and report any +suggestions or problems you might have via IRC or the issue tracker. + +If you want to contribute, please fork `the git repository +`_, create a new feature branch, make +your changes, and issue a pull request. Someone will review your changes as soon +as possible. Please refer to the :doc:`How to Contribute ` section +for more details. + +You can also contribute by creating themes and improving the documentation. + Is it mandatory to have a configuration file? ============================================= No, it's not. Configuration files are just an easy way to configure Pelican. For basic operations, it's possible to specify options while invoking Pelican -via the command line. See `pelican --help` for more information. +via the command line. See ``pelican --help`` for more information. I'm creating my own theme. How do I use Pygments for syntax highlighting? ========================================================================= Pygments adds some classes to the generated content. These classes are used by themes to style code syntax highlighting via CSS. Specifically, you can -customize the appearance of your syntax highlighting via the `.codehilite pre` +customize the appearance of your syntax highlighting via the ``.codehilite pre`` class in your theme's CSS file. To see how various styles can be used to render Django code, for example, you can use the demo `on the project website `_. @@ -25,19 +53,6 @@ How do I create my own theme? Please refer to :ref:`theming-pelican`. -How can I help? -================ - -There are several ways to help out. First, you can use Pelican and report any -suggestions or problems you might have on `the bugtracker -`_. - -If you want to contribute, please fork `the git repository -`_, make your changes, and issue -a pull request. I'll review your changes as soon as possible. - -You can also contribute by creating themes and improving the documentation. - I want to use Markdown, but I got an error. =========================================== @@ -49,3 +64,65 @@ install it. You can do so by typing:: In case you don't have pip installed, consider installing it via:: $ (sudo) easy_install pip + +Can I use arbitrary meta-data in my templates? +============================================== + +Yes. For example, to include a modified date in a Markdown post, one could +include the following at the top of the article:: + + Modified: 2012-08-08 + +That meta-data can then be accessed in the template:: + + {% if article.modified %} + Last modified: {{ article.modified }} + {% endif %} + +How do I assign custom templates on a per-page basis? +===================================================== + +It's as simple as adding an extra line of metadata to any pages or articles you +want to have its own template. + + :template: template_name + +Then just make sure to have the template installed in to your theme as +``template_name.html``. + +What if I want to disable feed generation? +========================================== + +To disable all feed generation set ``FEED_ATOM`` and ``FEED_RSS`` to ``None`` in +your settings. Please note ``None`` and ``''`` are not the same thing. The +word ``None`` should not be surrounded by quotes. + +I'm getting a warning about feeds generated without SITEURL being set properly +============================================================================== + +`RSS and Atom feeds require all URLs and links in them to be absolute +`_. +In order to properly generate all URLs properly in Pelican you will need to set +``SITEURL`` to the full path of your blog. When using ``make html`` and the +default Makefile provided by the `pelican-quickstart` bootstrap script to test +build your site, it's normal to see this warning since ``SITEURL`` is +deliberately left undefined. If configured properly no other ``make`` commands +should result in this warning. + +Feeds are still generated when this warning is displayed but may not validate. + +My feeds are broken since I upgraded to Pelican 3.0 +=================================================== + +Starting in 3.0, some of the FEED setting names were changed to more explicitly +refer to the Atom feeds they inherently represent (much like the FEED_RSS +setting names). Here is an exact list of the renamed setting names:: + + FEED -> FEED_ATOM + TAG_FEED -> TAG_FEED_ATOM + CATEGORY_FEED -> CATEGORY_FEED_ATOM + +Older 2.x themes that referenced the old setting names may not link properly. +In order to rectify this, please update your theme for compatibility with 3.0+ +by changing the relevant values in your template files. For an example of +complete feed headers and usage please check out the ``simple`` theme. diff --git a/docs/fr/configuration.rst b/docs/fr/configuration.rst index fadaf258..76f03a61 100644 --- a/docs/fr/configuration.rst +++ b/docs/fr/configuration.rst @@ -52,19 +52,19 @@ détails au prochain chapitre. Flux de syndication =================== -CATEGORY_FEED : +CATEGORY_FEED_ATOM : Chemin d’écriture des flux Atom liés aux catégories ; CATEGORY_FEED_RSS : Idem pour les flux rss (Optionnel); -FEED : +FEED_ATOM : Chemin du flux Atom global ; FEED_RSS : Chemin du flux Rss global (Optionnel); -TAG_FEED : +TAG_FEED_ATOM : Chemin des flux Atom pour les tags (Optionnel); TAG_FEED_RSS : @@ -77,8 +77,11 @@ Traductions DEFAULT_LANG : Le langage par défaut à utiliser. «*en*» par défaut ; -TRANSLATION_FEED : - Chemin du flux pour les traductions. +TRANSLATION_FEED_ATOM : + Chemin du flux Atom pour les traductions. + +TRANSLATION_FEED_RSS : + Chemin du flux RSS pour les traductions. Thèmes @@ -111,7 +114,7 @@ LINKS : PDF_PROCESSOR : Génère ou non les articles et pages au format pdf ; -REVERSE_ARCHIVE_ORDER : +NEWEST_FIRST_ARCHIVES : Met les articles plus récent en tête de l'archive ; SOCIAL : @@ -133,8 +136,12 @@ Pelican est fournit avec :doc:`pelican-themes`, un script permettant de gérer l Paramètres divers ================= -FALLBACK_ON_FS_DATE : - 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 ; +DEFAULT_DATE: + Date par défaut à utiliser si l'information de date n'est pas spécifiée + dans les metadonnées de l'article. + Si 'fs', Pelican se basera sur le *mtime* du fichier. + Si c'est un tuple, il sera passé au constructeur datetime.datetime pour + générer l'objet datetime utilisé par défaut. KEEP_OUTPUT DIRECTORY : Ne génère que les fichiers modifiés et n'efface pas le repertoire de sortie ; @@ -151,7 +158,5 @@ SITEURL : STATIC_PATHS : Les chemins statiques que vous voulez avoir accès sur le chemin de sortie "statique" ; - - - - +MARKDOWN_EXTENSIONS : + Liste des extentions Markdown que vous souhaitez utiliser ; diff --git a/docs/fr/index.rst b/docs/fr/index.rst index 9fd0c0f0..2deb5050 100644 --- a/docs/fr/index.rst +++ b/docs/fr/index.rst @@ -30,7 +30,7 @@ Code source =========== Vous pouvez accéder au code source via git à l'adresse -http://github.com/ametaireau/pelican/ +http://github.com/getpelican/pelican/ Feedback ! ========== diff --git a/docs/fr/installation.rst b/docs/fr/installation.rst index e853979d..da327725 100644 --- a/docs/fr/installation.rst +++ b/docs/fr/installation.rst @@ -24,7 +24,7 @@ Pour installer Pelican en reprenant le code via Github, nous aurons besoin du pa git-core pour récupérez les sources de Pelican. Puis nous procédons à l’installation :: # apt-get install git-core - $ git clone https://github.com/ametaireau/pelican.git + $ git clone https://github.com/getpelican/pelican.git $ cd pelican # python setup.py install diff --git a/docs/fr/themes.rst b/docs/fr/themes.rst index c6f45300..20d9d41f 100644 --- a/docs/fr/themes.rst +++ b/docs/fr/themes.rst @@ -10,7 +10,7 @@ Pelican utlise le très bon moteur de template `jinja2 ` pour produire de l'HTML. La syntaxe de jinja2 est vraiment très simple. Si vous voulez créer votre propre thème, soyez libre de prendre inspiration sur le theme "simple" qui est disponible `ici -`_ +`_ Structure ========= diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 8accf658..85a1b559 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -1,29 +1,32 @@ Getting started ############### -Installing -========== +Installing Pelican +================== You're ready? Let's go! You can install Pelican via several different methods. The simplest is via `pip `_:: $ pip install pelican -If you don't have pip installed, an alternative method is easy_install:: +If you don't have ``pip`` installed, an alternative method is ``easy_install``:: $ easy_install pelican While the above is the simplest method, the recommended approach is to create -a virtual environment for Pelican via `virtualenv `_ -and `virtualenvwrapper `_ -before installing Pelican:: +a virtual environment for Pelican via virtualenv_ and virtualenvwrapper_ before +installing Pelican. Assuming you've followed the virtualenvwrapper +`installation `_ +and `shell configuration +`_ +steps, you can then open a new terminal session and create a new virtual +environment for Pelican:: - $ pip install virtualenvwrapper $ mkvirtualenv pelican Once the virtual environment has been created and activated, Pelican can be -be installed via pip or easy_install as noted above. Alternatively, if you -have the project source, you can install Pelican using the distutils +be installed via ``pip`` or ``easy_install`` as noted above. Alternatively, if +you have the project source, you can install Pelican using the distutils method:: $ cd path-to-Pelican-source @@ -32,14 +35,19 @@ method:: If you have Git installed and prefer to install the latest bleeding-edge version of Pelican rather than a stable release, use the following command:: - $ pip install -e git://github.com/ametaireau/pelican#egg=pelican + $ pip install -e git://github.com/getpelican/pelican#egg=pelican + +If you plan on using Markdown as a markup format, you'll need to install the +Markdown library as well:: + + $ pip install Markdown Upgrading --------- -If you installed a stable Pelican release via pip or easy_install and wish to -upgrade to the latest stable release, you can do so by adding `--upgrade` to -the relevant command. For pip, that would be:: +If you installed a stable Pelican release via ``pip`` or ``easy_install`` and +wish to upgrade to the latest stable release, you can do so by adding +``--upgrade`` to the relevant command. For pip, that would be:: $ pip install --upgrade pelican @@ -55,12 +63,76 @@ At this time, Pelican is dependent on the following Python packages: * jinja2, for templating support * docutils, for supporting reStructuredText as an input format -If you're not using Python 2.7, you will also need `argparse`. +If you're not using Python 2.7, you will also need the ``argparse`` package. Optionally: * pygments, for syntax highlighting * Markdown, for supporting Markdown as an input format +* Typogrify, for typographical enhancements + +Kickstart a blog +================ + +Following is a brief tutorial for those who want to get started right away. +We're going to assume that virtualenv_ and virtualenvwrapper_ are installed and +configured; if you've installed Pelican outside of a virtual environment, +you can skip to the ``pelican-quickstart`` command. Let's first create a new +virtual environment and install Pelican into it:: + + $ mkvirtualenv pelican + $ pip install pelican Markdown + +Next we'll create a directory to house our site content and configuration files, +which can be located any place you prefer, and associate this new project with +the currently-active virtual environment:: + + $ mkdir ~/code/yoursitename + $ cd ~/code/yoursitename + $ setvirtualenvproject + +Now we can run the ``pelican-quickstart`` command, which will ask some questions +about your site:: + + $ pelican-quickstart + +Once you finish answering all the questions, you can begin adding content to the +*content* folder that has been created for you. (See *Writing articles using +Pelican* section below for more information about how to format your content.) +Once you have some content to generate, you can convert it to HTML via the +following command:: + + $ make html + +If you'd prefer to have Pelican automatically regenerate your site every time a +change is detected (handy when testing locally), use the following command +instead:: + + $ make regenerate + +To serve the site so it can be previewed in your browser at +http://localhost:8000:: + + $ make serve + +Normally you would need to run ``make regenerate`` and ``make serve`` in two +separate terminal sessions, but you can run both at once via:: + + $ make devserver + +The above command will simultaneously run Pelican in regeneration mode as well +as serve the output at http://localhost:8000. Once you are done testing your +changes, you should stop the development server via:: + + $ ./develop_server.sh stop + +When you're ready to publish your site, you can upload it via the method(s) you +chose during the ``pelican-quickstart`` questionnaire. For this example, we'll +use rsync over ssh:: + + $ make rsync_upload + +That's it! Your site should now be live. Writing articles using Pelican ============================== @@ -73,7 +145,7 @@ file system (for instance, about the category of your articles), but some information you need to provide in the form of metadata inside your files. You can provide this metadata in reStructuredText text files via the -following syntax (give your file the `.rst` extension):: +following syntax (give your file the ``.rst`` extension):: My super title ############## @@ -83,10 +155,14 @@ following syntax (give your file the `.rst` extension):: :category: yeah :author: Alexis Metaireau +Pelican implements an extension of reStructuredText to enable support for the +``abbr`` HTML tag. To use it, write something like this in your post:: -You can also use Markdown syntax (with a file ending in `.md`). -Markdown generation will not work until you explicitly install the `Markdown` -package, which can be done via `pip install Markdown`. Metadata syntax for + This will be turned into :abbr:`HTML (HyperText Markup Language)`. + +You can also use Markdown syntax (with a file ending in ``.md``). +Markdown generation will not work until you explicitly install the ``Markdown`` +package, which can be done via ``pip install Markdown``. Metadata syntax for Markdown posts should follow this pattern:: Date: 2010-12-03 @@ -99,54 +175,59 @@ Markdown posts should follow this pattern:: Note that, aside from the title, none of this metadata is mandatory: if the date is not specified, Pelican will rely on the file's "mtime" timestamp, and the category can be determined by the directory in which the file resides. For -example, a file located at `python/foobar/myfoobar.rst` will have a category of -`foobar`. +example, a file located at ``python/foobar/myfoobar.rst`` will have a category of +``foobar``. Generate your blog ------------------ -To launch Pelican, just use the `pelican` command:: +The ``make`` shortcut commands mentioned in the ``Kickstart a blog`` section +are mostly wrappers around the ``pelican`` command that generates the HTML from +the content. The ``pelican`` command can also be run directly:: $ pelican /path/to/your/content/ [-s path/to/your/settings.py] -And… that's all! Your weblog will be generated and saved in the `content/` -folder. +The above command will generate your weblog and save it in the ``output/`` +folder, using the default theme to produce a simple site. The default theme is +simple HTML without styling and is provided so folks may use it as a basis for +creating their own themes. -The above command will use the default theme to produce a simple site. It's not -very sexy, as it's just simple HTML output (without any style). - -You can create your own style if you want. Have a look at the help to see all -the options you can use:: +Pelican has other command-line switches available. Have a look at the help to +see all the options you can use:: $ pelican --help -Kickstart a blog ----------------- +Auto-reload +----------- -You also can use the `pelican-quickstart` script to start a new blog in -seconds by just answering a few questions. Just run `pelican-quickstart` and -you're done! (Added in Pelican 3.0) +It's possible to tell Pelican to watch for your modifications, instead of +manually re-running it every time you want to see your changes. To enable this, +run the ``pelican`` command with the ``-r`` or ``--autoreload`` option. Pages ----- -If you create a folder named `pages`, all the files in it will be used to +If you create a folder named ``pages``, all the files in it will be used to generate static pages. -Then, use the `DISPLAY_PAGES_ON_MENU` setting, which will add all the pages to +Then, use the ``DISPLAY_PAGES_ON_MENU`` setting, which will add all the pages to the menu. +If you want to exclude any pages from being linked to or listed in the menu +then add a ``status: hidden`` attribute to its metadata. This is useful for +things like making error pages that fit the generated theme of your site. + Importing an existing blog -------------------------- -It is possible to import your blog from Dotclear, WordPress, and RSS feeds using +It is possible to import your blog from Dotclear, WordPress, and RSS feeds using a simple script. See :ref:`import`. Translations ------------ -It is possible to translate articles. To do so, you need to add a `lang` meta -attribute to your articles/pages and set a `DEFAULT_LANG` setting (which is +It is possible to translate articles. To do so, you need to add a ``lang`` meta +attribute to your articles/pages and set a ``DEFAULT_LANG`` setting (which is English [en] by default). With those settings in place, only articles with the default language will be listed, and each article will be accompanied by a list of available translations for that article. @@ -191,26 +272,20 @@ Pelican is able to provide colorized syntax highlighting for your code blocks. To do so, you have to use the following conventions (you need to put this in your content files). -For RestructuredText:: +For RestructuredText, use the code-block directive:: .. code-block:: identifier - your code goes here + -For Markdown, format your code blocks thusly:: +For Markdown, include the language identifier just above the code block, +indenting both the identifier and code:: - :::identifier - your code goes here + :::identifier + -The specified identifier should be one that appears on the -`list of available lexers `_. - -Auto-reload ------------ - -It's possible to tell Pelican to watch for your modifications, instead of -manually re-running it every time you want to see your changes. To enable this, -run the `pelican` command with the `-r` or `--autoreload` option. +The specified identifier (e.g. ``python``, ``ruby``) should be one that +appears on the `list of available lexers `_. Publishing drafts ----------------- @@ -234,5 +309,5 @@ Or run a simple web server using Python:: cd output && python -m SimpleHTTPServer -(Tip: If using the latter method in conjunction with the auto-reload feature, -ensure that `DELETE_OUTPUT_DIRECTORY` is set to `False` in your settings file.) +.. _virtualenv: http://www.virtualenv.org/ +.. _virtualenvwrapper: http://www.doughellmann.com/projects/virtualenvwrapper/ diff --git a/docs/importer.rst b/docs/importer.rst index ccf3ffe2..ba96d9c2 100644 --- a/docs/importer.rst +++ b/docs/importer.rst @@ -31,7 +31,7 @@ BeatifulSoup can be installed like any other Python package:: $ pip install BeautifulSoup -For pandoc, install a package for your operating system from the +For pandoc, install a package for your operating system from the `pandoc site `_. diff --git a/docs/index.rst b/docs/index.rst index 34a1355c..3fc1cf9f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,16 +1,16 @@ Pelican -####### +======= -Pelican is a simple weblog generator, written in Python. +Pelican is a static site generator, written in Python_. -* Write your weblog entries directly with your editor of choice (vim!) in - reStructuredText or Markdown -* A simple CLI tool to (re)generate the weblog +* Write your weblog entries directly with your editor of choice (vim!) + in reStructuredText_ or Markdown_ +* Includes a simple CLI tool to (re)generate the weblog * Easy to interface with DVCSes and web hooks * Completely static output is easy to host anywhere Features -======== +-------- Pelican currently supports: @@ -18,40 +18,38 @@ Pelican currently supports: * Comments, via an external service (Disqus). (Please note that while useful, Disqus is an external service, and thus the comment data will be somewhat outside of your control and potentially subject to data loss.) -* Theming support (themes are created using `jinja2 `_) +* Theming support (themes are created using Jinja2_ templates) * PDF generation of the articles/pages (optional) * Publication of articles in multiple languages * Atom/RSS feeds * Code syntax highlighting -* Compilation of less css (optional) +* Compilation of `LESS CSS`_ (optional) * Import from WordPress, Dotclear, or RSS feeds * Integration with external tools: Twitter, Google Analytics, etc. (optional) -Why the name "Pelican" ? -======================== +Why the name "Pelican"? +----------------------- -Heh, you didn't notice? "Pelican" is an anagram for « Calepin » ;) +"Pelican" is an anagram for *calepin*, which means "notebook" in French. ;) Source code -=========== +----------- -You can access the source code via git at http://github.com/ametaireau/pelican/ +You can access the source code at: https://github.com/getpelican/pelican Feedback / Contact us -===================== +--------------------- -If you want to see new features in Pelican, don't hesitate to tell me, to clone -the repository, etc. That's open source, dude! +If you want to see new features in Pelican, don't hesitate to offer suggestions, +clone the repository, etc. There are many ways to :doc:`contribute`. +That's open source, dude! -Contact me at "alexis at notmyidea dot org" for any request/feedback! You can -also join the team at `#pelican on irc.freenode.org -`_ -(or if you don't have any IRC client, use `the webchat -`_) -for quick feedback. +Send a message to "authors at getpelican dot com" with any requests/feedback! You +can also join the team at `#pelican on Freenode`_ (or if you don't have an IRC +client handy, use the webchat_ for quick feedback. Documentation -============= +------------- A French version of the documentation is available at :doc:`fr/index`. @@ -69,3 +67,16 @@ A French version of the documentation is available at :doc:`fr/index`. tips contribute report + changelog + +.. Links + +.. _Python: http://www.python.org/ +.. _reStructuredText: http://docutils.sourceforge.net/rst.html +.. _Markdown: http://daringfireball.net/projects/markdown/ +.. _Jinja2: http://jinja.pocoo.org/ +.. _`LESS CSS`: http://lesscss.org/ +.. _`Pelican documentation`: http://docs.getpelican.com/latest/ +.. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html +.. _`#pelican on Freenode`: irc://irc.freenode.net/pelican +.. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4 diff --git a/docs/internals.rst b/docs/internals.rst index f0934825..a6264476 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -12,34 +12,34 @@ original author wrote with some software design information. Overall structure ================= -What `pelican` does is take a list of files and process them into some +What Pelican does is take a list of files and process them into some sort of output. Usually, the input files are reStructuredText and Markdown files, and the output is a blog, but both input and output can be anything you want. The logic is separated into different classes and concepts: -* `writers` are responsible for writing files: .html files, RSS feeds, and so +* **Writers** are responsible for writing files: .html files, RSS feeds, and so on. Since those operations are commonly used, the object is created once and then passed to the generators. -* `readers` are used to read from various formats (Markdown and +* **Readers** are used to read from various formats (Markdown and reStructuredText for now, but the system is extensible). Given a file, they return metadata (author, tags, category, etc.) and content (HTML-formatted). -* `generators` generate the different outputs. For instance, Pelican comes with - `ArticlesGenerator` and `PageGenerator`. Given a configuration, they can do +* **Generators** generate the different outputs. For instance, Pelican comes with + ``ArticlesGenerator`` and ``PageGenerator``. Given a configuration, they can do whatever they want. Most of the time, it's generating files from inputs. -* `pelican` also uses `templates`, so it's easy to write your own theme. The - syntax is `jinja2`, and, trust me, really easy to learn, so don't hesitate - to jump in and build your own theme. +* Pelican also uses templates, so it's easy to write your own theme. The + syntax is `Jinja2 `_ and is very easy to learn, so + don't hesitate to jump in and build your own theme. How to implement a new reader? ============================== Is there an awesome markup language you want to add to Pelican? -Well, the only thing you have to do is to create a class with a `read` +Well, the only thing you have to do is to create a class with a ``read`` method that returns HTML content and some metadata. Take a look at the Markdown reader:: @@ -52,7 +52,7 @@ Take a look at the Markdown reader:: text = open(filename) md = Markdown(extensions = ['meta', 'codehilite']) content = md.convert(text) - + metadata = {} for name, value in md.Meta.items(): if name in _METADATA_FIELDS: @@ -65,8 +65,8 @@ Take a look at the Markdown reader:: Simple, isn't it? If your new reader requires additional Python dependencies, then you should wrap -their `import` statements in a `try...except` block. Then inside the reader's -class, set the `enabled` class attribute to mark import success or failure. +their ``import`` statements in a ``try...except`` block. Then inside the reader's +class, set the ``enabled`` class attribute to mark import success or failure. This makes it possible for users to continue using their favourite markup method without needing to install modules for formats they don't use. @@ -76,17 +76,17 @@ How to implement a new generator? Generators have two important methods. You're not forced to create both; only the existing ones will be called. -* `generate_context`, that is called first, for all the generators. +* ``generate_context``, that is called first, for all the generators. Do whatever you have to do, and update the global context if needed. This context is shared between all generators, and will be passed to the - templates. For instance, the `PageGenerator` `generate_context` method finds - all the pages, transforms them into objects, and populates the context with - them. Be careful *not* to output anything using this context at this stage, - as it is likely to change by the effect of other generators. + templates. For instance, the ``PageGenerator`` ``generate_context`` method + finds all the pages, transforms them into objects, and populates the context + with them. Be careful *not* to output anything using this context at this + stage, as it is likely to change by the effect of other generators. -* `generate_output` is then called. And guess what is it made for? Oh, +* ``generate_output`` is then called. And guess what is it made for? Oh, generating the output. :) It's here that you may want to look at the context - and call the methods of the `writer` object that is passed as the first - argument of this function. In the `PageGenerator` example, this method will + and call the methods of the ``writer`` object that is passed as the first + argument of this function. In the ``PageGenerator`` example, this method will look at all the pages recorded in the global context and output a file on - the disk (using the writer method `write_file`) for each page encountered. + the disk (using the writer method ``write_file``) for each page encountered. diff --git a/docs/pelican-themes.rst b/docs/pelican-themes.rst index c7cbc5b7..23be8355 100644 --- a/docs/pelican-themes.rst +++ b/docs/pelican-themes.rst @@ -64,7 +64,7 @@ In this example, we can see there are three themes available: ``notmyidea``, ``s Note that you can combine the ``--list`` option with the ``-v`` or ``--verbose`` option to get more verbose output, like this: .. code-block:: console - + $ pelican-themes -v -l /usr/local/lib/python2.6/dist-packages/pelican-2.6.0-py2.6.egg/pelican/themes/notmyidea /usr/local/lib/python2.6/dist-packages/pelican-2.6.0-py2.6.egg/pelican/themes/two-column (symbolic link to `/home/skami/Dev/Python/pelican-themes/two-column') @@ -118,7 +118,7 @@ Creating symbolic links To symbolically link a theme, you can use the ``-s`` or ``--symlink``, which works exactly as the ``--install`` option: .. code-block:: console - + # pelican-themes --symlink ~/Dev/Python/pelican-themes/two-column In this example, the ``two-column`` theme is now symbolically linked to the Pelican themes path, so we can use it, but we can also modify it without having to reinstall it after each modification. @@ -130,11 +130,11 @@ This is useful for theme development: $ sudo pelican-themes -s ~/Dev/Python/pelican-themes/two-column $ pelican ~/Blog/content -o /tmp/out -t two-column $ firefox /tmp/out/index.html - $ vim ~/Dev/Pelican/pelican-themes/two-coumn/static/css/main.css + $ vim ~/Dev/Pelican/pelican-themes/two-coumn/static/css/main.css $ pelican ~/Blog/content -o /tmp/out -t two-column $ cp /tmp/bg.png ~/Dev/Pelican/pelican-themes/two-coumn/static/img/bg.png $ pelican ~/Blog/content -o /tmp/out -t two-column - $ vim ~/Dev/Pelican/pelican-themes/two-coumn/templates/index.html + $ vim ~/Dev/Pelican/pelican-themes/two-coumn/templates/index.html $ pelican ~/Blog/content -o /tmp/out -t two-column @@ -152,7 +152,7 @@ The ``--install``, ``--remove`` and ``--symlink`` option are not mutually exclus --symlink ~/Dev/Python/pelican-themes/two-column \ --verbose -In this example, the theme ``notmyidea-cms`` is replaced by the theme ``notmyidea-cms-fr`` +In this example, the theme ``notmyidea-cms`` is replaced by the theme ``notmyidea-cms-fr`` @@ -162,5 +162,3 @@ See also - http://docs.notmyidea.org/alexis/pelican/ - ``/usr/share/doc/pelican/`` if you have installed Pelican using the `APT repository `_ - - diff --git a/docs/plugins.rst b/docs/plugins.rst index db5a4bfc..c275c57c 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -3,41 +3,41 @@ Plugins ####### -Since version 3.0, pelican manages plugins. Plugins are a way to add features -to pelican without having to directly hack pelican code. +Since version 3.0, Pelican manages plugins. Plugins are a way to add features +to Pelican without having to directly hack Pelican code. Pelican is shipped with a set of core plugins, but you can easily implement your own (and this page describes how). -How to use plugins? -==================== +How to use plugins +================== To load plugins, you have to specify them in your settings file. You have two ways to do so. Either by specifying strings with the path to the callables:: - PLUGINS = ['pelican.plugins.gravatar',] + PLUGINS = ['pelican.plugins.gravatar',] Or by importing them and adding them to the list:: from pelican.plugins import gravatar PLUGINS = [gravatar, ] -If your plugins are not in an importable path, you can specify a `PLUGIN_PATH` +If your plugins are not in an importable path, you can specify a ``PLUGIN_PATH`` in the settings:: PLUGIN_PATH = "plugins" PLUGINS = ["list", "of", "plugins"] -How to create plugins? -====================== +How to create plugins +===================== -Plugins are based on the concept of signals. Pelican sends signals and plugins +Plugins are based on the concept of signals. Pelican sends signals, and plugins subscribe to those signals. The list of signals are defined in a following section. -The only rule to follow for plugins is to define a `register` callable, in -which you map the signals to your plugin logic. Let's take a simple exemple:: +The only rule to follow for plugins is to define a ``register`` callable, in +which you map the signals to your plugin logic. Let's take a simple example:: from pelican import signals @@ -48,41 +48,79 @@ which you map the signals to your plugin logic. Let's take a simple exemple:: signals.initialized.connect(test) + List of signals =============== Here is the list of currently implemented signals: -========================= ============================ ========================================= -Signal Arguments Description -========================= ============================ ========================================= -initialized pelican object -article_generate_context article_generator, metadata -article_generator_init article_generator invoked in the ArticlesGenerator.__init__ -========================= ============================ ========================================= +============================= ============================ =========================================================================== +Signal Arguments Description +============================= ============================ =========================================================================== +initialized pelican object +finalized pelican object invoked after all the generators are executed and just before pelican exits + usefull for custom post processing actions, such as: + - minifying js/css assets. + - notify/ping search engines with an updated sitemap. +article_generate_context article_generator, metadata +article_generator_init article_generator invoked in the ArticlesGenerator.__init__ +article_generator_finalized article_generator invoked at the end of ArticlesGenerator.generate_context +get_generators generators invoked in Pelican.get_generator_classes, + can return a Generator, or several + generator in a tuple or in a list. +pages_generate_context pages_generator, metadata +pages_generator_init pages_generator invoked in the PagesGenerator.__init__ +============================= ============================ =========================================================================== The list is currently small, don't hesitate to add signals and make a pull request if you need them! +.. note:: + + The signal ``content_object_init`` can send different type of object as + argument. If you want to register only one type of object then you will + need to specify the sender when you are connecting to the signal. + + :: + + from pelican import signals + from pelican import contents + + def test(sender, instance): + print "%s : %s content initialized !!" % (sender, instance) + + def register(): + signals.content_object_init.connect(test, sender=contents.Article) + + + List of plugins =============== -Not all the list are described here, but a few of them have been extracted from -pelican core and provided in pelican.plugins. They are described here: +The following plugins are currently included with Pelican under ``pelican.plugins``: -Tag cloud ---------- +* `GitHub activity`_ +* `Global license`_ +* `Gravatar`_ +* `HTML tags for reStructuredText`_ +* `Related posts`_ +* `Sitemap`_ -Translation ------------ +Ideas for plugins that haven't been written yet: -Github Activity +* Tag cloud +* Translation + +Plugin descriptions +=================== + +GitHub activity --------------- This plugin makes use of the ``feedparser`` library that you'll need to install. -Set the GITHUB_ACTIVITY_FEED parameter to your github activity feed. +Set the ``GITHUB_ACTIVITY_FEED`` parameter to your GitHub activity feed. For example, my setting would look like:: GITHUB_ACTIVITY_FEED = 'https://github.com/kpanic.atom' @@ -105,4 +143,132 @@ variable, as in the example:: ``github_activity`` is a list of lists. The first element is the title -and the second element is the raw html from github. +and the second element is the raw HTML from GitHub. + +Global license +-------------- + +This plugin allows you to define a LICENSE setting and adds the contents of that +license variable to the article's context, making that variable available to use +from within your theme's templates. + +Gravatar +-------- + +This plugin assigns the ``author_gravatar`` variable to the Gravatar URL and +makes the variable available within the article's context. You can add +AUTHOR_EMAIL to your settings file to define the default author's email +address. Obviously, that email address must be associated with a Gravatar +account. + +Alternatively, you can provide an email address from within article metadata:: + + :email: john.doe@example.com + +If the email address is defined via at least one of the two methods above, +the ``author_gravatar`` variable is added to the article's context. + +HTML tags for reStructuredText +------------------------------ + +This plugin allows you to use HTML tags from within reST documents. Following +is a usage example, which is in this case a contact form:: + + .. html:: + +
+

+ +
+ +
+ +

+
+ +Related posts +------------- + +This plugin adds the ``related_posts`` variable to the article's context. +To enable, add the following to your settings file:: + + from pelican.plugins import related_posts + PLUGINS = [related_posts] + +You can then use the ``article.related_posts`` variable in your templates. +For example:: + + {% if article.related_posts %} +
    + {% for related_post in article.related_posts %} +
  • {{ related_post }}
  • + {% endfor %} +
+ {% endif %} + +Sitemap +------- + +The sitemap plugin generates plain-text or XML sitemaps. You can use the +``SITEMAP`` variable in your settings file to configure the behavior of the +plugin. + +The ``SITEMAP`` variable must be a Python dictionary, it can contain three keys: + +- ``format``, which sets the output format of the plugin (``xml`` or ``txt``) + +- ``priorities``, which is a dictionary with three keys: + + - ``articles``, the priority for the URLs of the articles and their + translations + + - ``pages``, the priority for the URLs of the static pages + + - ``indexes``, the priority for the URLs of the index pages, such as tags, + author pages, categories indexes, archives, etc... + + All the values of this dictionary must be decimal numbers between ``0`` and ``1``. + +- ``changefreqs``, which is a dictionary with three items: + + - ``articles``, the update frequency of the articles + + - ``pages``, the update frequency of the pages + + - ``indexes``, the update frequency of the index pages + + Valid frequency values are ``always``, ``hourly``, ``daily``, ``weekly``, ``monthly``, + ``yearly`` and ``never``. + +If a key is missing or a value is incorrect, it will be replaced with the +default value. + +The sitemap is saved in ``/sitemap.``. + +.. note:: + ``priorities`` and ``changefreqs`` are informations for search engines. + They are only used in the XML sitemaps. + For more information: + +**Example** + +Here is an example configuration (it's also the default settings): + +.. code-block:: python + + PLUGINS=['pelican.plugins.sitemap',] + + SITEMAP = { + 'format': 'xml', + 'priorities': { + 'articles': 0.5, + 'indexes': 0.5, + 'pages': 0.5 + }, + 'changefreqs': { + 'articles': 'monthly', + 'indexes': 'daily', + 'pages': 'monthly' + } + } diff --git a/docs/report.rst b/docs/report.rst index 7e0432e2..f3ddff31 100644 --- a/docs/report.rst +++ b/docs/report.rst @@ -1,40 +1,40 @@ -Some history about pelican +Some history about Pelican ########################## .. warning:: This page comes from a report the original author (Alexis Métaireau) wrote - right after writing pelican, in december 2010. The information may not be - up to date. + right after writing Pelican, in December 2010. The information may not be + up-to-date. Pelican is a simple static blog generator. It parses markup files -(markdown or restructured text for now), and generate a HTML folder +(Markdown or reStructuredText for now) and generates an HTML folder with all the files in it. -I've chosen to use python to implement pelican because it seemed to +I've chosen to use Python to implement Pelican because it seemed to be simple and to fit to my needs. I did not wanted to define a class for each thing, but still wanted to keep my things loosely coupled. It turns out that it was exactly what I wanted. From time to time, thanks to the feedback of some users, it took me a very few time to -provide fixes on it. So far, I've re-factored the pelican code by two -times, each time took less than 30 minutes. +provide fixes on it. So far, I've re-factored the Pelican code by two +times; each time took less than 30 minutes. Use case ======== -I was previously using wordpress, a solution you can host on a web +I was previously using WordPress, a solution you can host on a web server to manage your blog. Most of the time, I prefer using markup -languages such as Markdown or RestructuredText to type my articles. +languages such as Markdown or reStructuredText to type my articles. To do so, I use vim. I think it is important to let the people choose the tool they want to write the articles. In my opinion, a blog manager should just allow you to take any kind of input and transform it to a -weblog. That's what pelican does. +weblog. That's what Pelican does. You can write your articles using the tool you want, and the markup -language you want, and then generate a static HTML weblog +language you want, and then generate a static HTML weblog. .. image:: _static/overall.png -To be flexible enough, pelican have a template support, so you can -easily write you own themes if you want to. +To be flexible enough, Pelican has template support, so you can easily write +your own themes if you want to. Design process ============== @@ -42,19 +42,18 @@ Design process Pelican came from a need I have. I started by creating a single file application, and I have make it grow to support what it does by now. To start, I wrote a piece of documentation about what I wanted to do. -Then, I have created the content I wanted to parse (the restructured -text files), and started experimenting with the code. -Pelican was 200 lines long, and contained almost ten functions and one -class when it was first usable. +Then, I created the content I wanted to parse (the reStructuredText files) +and started experimenting with the code. Pelican was 200 lines long and +contained almost ten functions and one class when it was first usable. -I have been facing different problems all over the time, and wanted to -add features to pelican while using it. The first change I have done was +I have been facing different problems all over the time and wanted to +add features to Pelican while using it. The first change I have done was to add the support of a settings file. It is possible to pass the options to the command line, but can be tedious if there is a lot of them. In the same way, I have added the support of different things over -time: atom feeds, multiple themes, multiple markup support, etc. -At some point, it appears that the “only one file” mantra was not good -enough for pelican, so I decided to rework a bit all that, and split this in +time: Atom feeds, multiple themes, multiple markup support, etc. +At some point, it appears that the "only one file" mantra was not good +enough for Pelican, so I decided to rework a bit all that, and split this in multiple different files. I’ve separated the logic in different classes and concepts: @@ -64,59 +63,59 @@ I’ve separated the logic in different classes and concepts: Since those operations are commonly used, the object is created once, and then passed to the generators. -* *readers* are used to read from various formats (Markdown, and - Restructured Text for now, but the system is extensible). Given a - file, they return metadata (author, tags, category etc) and - content (HTML formated). +* *readers* are used to read from various formats (Markdown and + reStructuredText for now, but the system is extensible). Given a + file, they return metadata (author, tags, category, etc) and + content (HTML formatted). -* *generators* generate the different outputs. For instance, pelican +* *generators* generate the different outputs. For instance, Pelican comes with an ArticlesGenerator and PagesGenerator, into others. Given a configuration, they can do whatever you want - them to do. Most of the time it’s generating files from inputs + them to do. Most of the time it's generating files from inputs (user inputs and files). -I also deal with contents objects. They can be `Articles`, `Pages`, `Quotes`, -or whatever you want. They are defined in the contents.py module, -and represent some content to be used by the program. +I also deal with contents objects. They can be ``Articles``, ``Pages``, +``Quotes``, or whatever you want. They are defined in the ``contents.py`` +module and represent some content to be used by the program. -In more details -=============== +In more detail +============== -Here is an overview of the classes involved in pelican. +Here is an overview of the classes involved in Pelican. .. image:: _static/uml.jpg -The interface do not really exists, and I have added it only to clarify the -whole picture. I do use duck typing, and not interfaces. +The interface does not really exist, and I have added it only to clarify the +whole picture. I do use duck typing and not interfaces. Internally, the following process is followed: * First of all, the command line is parsed, and some content from - the user are used to initialize the different generator objects. + the user is used to initialize the different generator objects. -* A `context` is created. It contains the settings from the command +* A ``context`` is created. It contains the settings from the command line and a settings file if provided. -* The `generate_context` method of each generator is called, updating +* The ``generate_context`` method of each generator is called, updating the context. -* The writer is created, and given to the `generate_output` method of +* The writer is created and given to the ``generate_output`` method of each generator. I make two calls because it is important that when the output is generated by the generators, the context will not change. In other -words, the first method `generate_context` should modify the context, -whereas the second `generate_output` method should not. +words, the first method ``generate_context`` should modify the context, +whereas the second ``generate_output`` method should not. Then, it is up to the generators to do what the want, in the -`generate_context` and `generate_content` method. -Taking the `ArticlesGenerator` class will help to understand some others -concepts. Here is what happens when calling the `generate_context` +``generate_context`` and ``generate_content`` method. +Taking the ``ArticlesGenerator`` class will help to understand some others +concepts. Here is what happens when calling the ``generate_context`` method: * Read the folder “path”, looking for restructured text files, load - each of them, and construct a content object (`Article`) with it. To do so, - use `Reader` objects. -* Update the `context` with all those articles. + each of them, and construct a content object (``Article``) with it. To do so, + use ``Reader`` objects. +* Update the ``context`` with all those articles. -Then, the `generate_content` method uses the `context` and the `writer` to -generate the wanted output +Then, the ``generate_content`` method uses the ``context`` and the ``writer`` to +generate the wanted output. diff --git a/docs/settings.rst b/docs/settings.rst index 5eb97edd..af6bc8c0 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -8,10 +8,16 @@ the command line:: Settings are configured in the form of a Python module (a file). You can see an example by looking at `/samples/pelican.conf.py -`_ +`_ All the setting identifiers must be set in all-caps, otherwise they will not be -processed. +processed. Setting values that are numbers (5, 20, etc.), booleans (True, +False, None, etc.), dictionaries, or tuples should *not* be enclosed in +quotation marks. All other values (i.e., strings) *must* be enclosed in +quotation marks. + +Unless otherwise specified, settings that refer to paths can be either absolute or relative to the +configuration file. The settings you define in the configuration file will be passed to the templates, which allows you to use your settings to add site-wide content. @@ -33,12 +39,19 @@ Setting name (default value) What doe `DISPLAY_PAGES_ON_MENU` (``True``) Whether to display pages on the menu of the template. Templates may or not honor this setting. -`FALLBACK_ON_FS_DATE` (``True``) If True, Pelican will use the file system +`DEFAULT_DATE` (``fs``) The default date you want to use. + If 'fs', Pelican will use the file system timestamp information (mtime) if it can't get date information from the metadata. + If tuple object, it will instead generate the + default datetime object by passing the tuple to + the datetime.datetime constructor. +`DELETE_OUTPUT_DIRECTORY` (``False``) Delete the content of the output directory before + generating new files. +`FILES_TO_COPY` (``()``) A list of files to copy from the source (inside the content + directory) to the destination (inside the output directory). + For example: ``(('extra/robots.txt', 'robots.txt'),)``. `JINJA_EXTENSIONS` (``[]``) A list of any Jinja2 extensions you want to use. -`DELETE_OUTPUT_DIRECTORY` (``False``) Delete the output directory as well as - the generated files. `LOCALE` (''[#]_) Change the locale. A list of locales can be provided here or a single string representing one locale. When providing a list, all the locales will be tried @@ -51,26 +64,30 @@ Setting name (default value) What doe Python-Markdown documentation for a complete list of supported extensions. `OUTPUT_PATH` (``'output/'``) Where to output the generated files. -`PATH` (``None``) Path to look at for input files. -`PAGE_DIR` (``'pages'``) Directory to look at for pages. +`PATH` (``None``) Path to content directory to be processed by Pelican. +`PAGE_DIR` (``'pages'``) Directory to look at for pages, relative to `PATH`. `PAGE_EXCLUDES` (``()``) A list of directories to exclude when looking for pages. -`ARTICLE_DIR` (``''``) Directory to look at for articles. +`ARTICLE_DIR` (``''``) Directory to look at for articles, relative to `PATH`. `ARTICLE_EXCLUDES`: (``('pages',)``) A list of directories to exclude when looking for articles. `PDF_GENERATOR` (``False``) Set to True if you want to have PDF versions of your documents. You will need to install `rst2pdf`. -`RELATIVE_URLS` (``True``) Defines whether Pelican should use relative URLs or - not. +`OUTPUT_SOURCES` (``False``) Set to True if you want to copy the articles and pages in their + original format (e.g. Markdown or ReStructeredText) to the + specified OUTPUT_PATH. +`OUTPUT_SOURCES_EXTENSION` (``.text``) Controls the extension that will be used by the SourcesGenerator. + Defaults to ``.text``. If not a valid string the default value + will be used. +`RELATIVE_URLS` (``True``) Defines whether Pelican should use document-relative URLs or + not. If set to ``False``, Pelican will use the SITEURL + setting to construct absolute URLs. `PLUGINS` (``[]``) The list of plugins to load. See :ref:`plugins`. `SITENAME` (``'A Pelican Blog'``) Your site name `SITEURL` Base URL of your website. Not defined by default, - which means the base URL is assumed to be "/" with a - root-relative URL structure. If `SITEURL` is specified - explicitly, there should be no trailing slash at the end, - and URLs will be generated with an absolute URL structure - (including the domain). If you want to use relative URLs - instead of root-relative or absolute URLs, you should - instead use the `RELATIVE_URL` setting. + so it is best to specify your SITEURL; if you do not, feeds + will not be generated with properly-formed URLs. You should + include ``http://`` and your domain, with no trailing + slash at the end. Example: ``SITEURL = 'http://mydomain.com'`` `STATIC_PATHS` (``['images']``) The static paths you want to have accessible on the output path "static". By default, Pelican will copy the 'images' folder to the @@ -78,11 +95,10 @@ Setting name (default value) What doe `TIMEZONE` The timezone used in the date information, to generate Atom and RSS feeds. See the "timezone" section below for more info. -`TYPOGRIFY` (``False``) If set to true, some - additional transformations will be done on the - generated HTML, using the `Typogrify +`TYPOGRIFY` (``False``) If set to True, several typographical improvements will be + incorporated into the generated HTML via the `Typogrify `_ - library + library, which can be installed via: ``pip install typogrify`` `LESS_GENERATOR` (``FALSE``) Set to True or complete path to `lessc` (if not found in system PATH) to enable compiling less css files. Requires installation of `less css`_. @@ -91,12 +107,17 @@ Setting name (default value) What doe index pages for collections of content e.g. tags and category index pages. `PAGINATED_DIRECT_TEMPLATES` (``('index',)``) Provides the direct templates that should be paginated. -`SUMMARY_MAX_LENGTH` (``50``) When creating a short summary of an article, this will +`SUMMARY_MAX_LENGTH` (``50``) When creating a short summary of an article, this will be the default length in words of the text created. - This only applies if your content does not otherwise - specify a summary. Setting to None will cause the summary + This only applies if your content does not otherwise + specify a summary. Setting to None will cause the summary to be a copy of the original content. - +`EXTRA_TEMPLATES_PATHS` (``[]``) A list of paths you want Jinja2 to look for the templates. + Can be used to separate templates from the theme. + Example: projects, resume, profile ... + This templates need to use ``DIRECT_TEMPLATES`` setting + +`MARKDOWN_EXTENSIONS` (``['toc',]``) A list of any Markdown extensions you want to use. ===================================================================== ===================================================================== .. [#] Default is the system locale. @@ -107,6 +128,15 @@ Setting name (default value) What doe URL settings ------------ +The first thing to understand is that there are currently two supported methods +for URL formation: *relative* and *absolute*. Document-relative URLs are useful +when testing locally, and absolute URLs are reliable and most useful when +publishing. One method of supporting both is to have one Pelican configuration +file for local development and another for publishing. To see an example of this +type of setup, use the ``pelican-quickstart`` script as described at the top of +the :doc:`Getting Started` page, which will produce two separate +configuration files for local development and publishing, respectively. + You can customize the URLs and locations where files will be saved. The URLs and SAVE_AS variables use Python's format strings. These variables allow you to place your articles in a location such as '{slug}/index.html' and link to them as @@ -131,37 +161,37 @@ Also, you can use other file metadata attributes as well: Example usage: -* ARTICLE_URL = 'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/' -* ARTICLE_SAVE_AS = 'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/index.html' +* ARTICLE_URL = ``'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/'`` +* ARTICLE_SAVE_AS = ``'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/index.html'`` This would save your articles in something like '/posts/2011/Aug/07/sample-post/index.html', and the URL to this would be '/posts/2011/Aug/07/sample-post/'. -================================================ ===================================================== -Setting name (default value) what does it do? -================================================ ===================================================== -`ARTICLE_URL` ('{slug}.html') The URL to refer to an ARTICLE. -`ARTICLE_SAVE_AS` ('{slug}.html') The place where we will save an article. -`ARTICLE_LANG_URL` ('{slug}-{lang}.html') The URL to refer to an ARTICLE which doesn't use the - default language. -`ARTICLE_LANG_SAVE_AS` ('{slug}-{lang}.html' The place where we will save an article which - doesn't use the default language. -`PAGE_URL` ('pages/{slug}.html') The URL we will use to link to a page. -`PAGE_SAVE_AS` ('pages/{slug}.html') The location we will save the page. -`PAGE_LANG_URL` ('pages/{slug}-{lang}.html') The URL we will use to link to a page which doesn't - use the default language. -`PAGE_LANG_SAVE_AS` ('pages/{slug}-{lang}.html') The location we will save the page which doesn't - use the default language. -`AUTHOR_URL` ('author/{slug}.html') The URL to use for an author. -`AUTHOR_SAVE_AS` ('author/{slug}.html') The location to save an author. -`CATEGORY_URL` ('category/{slug}.html') The URL to use for a category. -`CATEGORY_SAVE_AS` ('category/{slug}.html') The location to save a category. -`TAG_URL` ('tag/{slug}.html') The URL to use for a tag. -`TAG_SAVE_AS` ('tag/{slug}.html') The location to save the tag page. -`_SAVE_AS` The location to save content generated from direct - templates. Where is the - upper case template name. -================================================ ===================================================== +==================================================== ===================================================== +Setting name (default value) What does it do? +==================================================== ===================================================== +`ARTICLE_URL` (``'{slug}.html'``) The URL to refer to an ARTICLE. +`ARTICLE_SAVE_AS` (``'{slug}.html'``) The place where we will save an article. +`ARTICLE_LANG_URL` (``'{slug}-{lang}.html'``) The URL to refer to an ARTICLE which doesn't use the + default language. +`ARTICLE_LANG_SAVE_AS` (``'{slug}-{lang}.html'``) The place where we will save an article which + doesn't use the default language. +`PAGE_URL` (``'pages/{slug}.html'``) The URL we will use to link to a page. +`PAGE_SAVE_AS` (``'pages/{slug}.html'``) The location we will save the page. +`PAGE_LANG_URL` (``'pages/{slug}-{lang}.html'``) The URL we will use to link to a page which doesn't + use the default language. +`PAGE_LANG_SAVE_AS` (``'pages/{slug}-{lang}.html'``) The location we will save the page which doesn't + use the default language. +`AUTHOR_URL` (``'author/{name}.html'``) The URL to use for an author. +`AUTHOR_SAVE_AS` (``'author/{name}.html'``) The location to save an author. +`CATEGORY_URL` (``'category/{name}.html'``) The URL to use for a category. +`CATEGORY_SAVE_AS` (``'category/{name}.html'``) The location to save a category. +`TAG_URL` (``'tag/{name}.html'``) The URL to use for a tag. +`TAG_SAVE_AS` (``'tag/{name}.html'``) The location to save the tag page. +`_SAVE_AS` The location to save content generated from direct + templates. Where is the + upper case template name. +==================================================== ===================================================== .. note:: @@ -184,14 +214,14 @@ Have a look at `the wikipedia page`_ to get a list of valid timezone values. Date format and locale ---------------------- -If no DATE_FORMAT is set, fall back to DEFAULT_DATE_FORMAT. If you need to +If no DATE_FORMATS is set, fall back to DEFAULT_DATE_FORMAT. If you need to maintain multiple languages with different date formats, you can set this dict using language name (``lang`` in your posts) as key. Regarding available format codes, see `strftime document of python`_ : .. parsed-literal:: - DATE_FORMAT = { + DATE_FORMATS = { 'en': '%a, %d %b %Y', 'jp': '%Y-%m-%d(%a)', } @@ -210,13 +240,13 @@ above: .. parsed-literal:: # On Unix/Linux - DATE_FORMAT = { + DATE_FORMATS = { 'en': ('en_US','%a, %d %b %Y'), 'jp': ('ja_JP','%Y-%m-%d(%a)'), } # On Windows - DATE_FORMAT = { + DATE_FORMATS = { 'en': ('usa','%a, %d %b %Y'), 'jp': ('jpn','%Y-%m-%d(%a)'), } @@ -240,7 +270,7 @@ feeds if you prefer. Pelican generates category feeds as well as feeds for all your articles. It does not generate feeds for tags by default, but it is possible to do so using -the ``TAG_FEED`` and ``TAG_FEED_RSS`` settings: +the ``TAG_FEED_ATOM`` and ``TAG_FEED_RSS`` settings: ================================================ ===================================================== Setting name (default value) What does it do? @@ -251,11 +281,11 @@ Setting name (default value) What does it do? you have already explicitly defined SITEURL (see above) and want to use the same domain for your feeds, you can just set: `FEED_DOMAIN = SITEURL` -`FEED` (``'feeds/all.atom.xml'``) Relative URL to output the Atom feed. +`FEED_ATOM` (``'feeds/all.atom.xml'``) Relative URL to output the Atom feed. `FEED_RSS` (``None``, i.e. no RSS) Relative URL to output the RSS feed. -`CATEGORY_FEED` ('feeds/%s.atom.xml'[2]_) Where to put the category Atom feeds. +`CATEGORY_FEED_ATOM` ('feeds/%s.atom.xml'[2]_) Where to put the category Atom feeds. `CATEGORY_FEED_RSS` (``None``, i.e. no RSS) Where to put the category RSS feeds. -`TAG_FEED` (``None``, i.e. no tag feed) Relative URL to output the tag Atom feed. It should +`TAG_FEED_ATOM` (``None``, i.e. no tag feed) Relative URL to output the tag Atom feed. It should be defined using a "%s" match in the tag name. `TAG_FEED_RSS` (``None``, ie no RSS tag feed) Relative URL to output the tag RSS feed `FEED_MAX_ITEMS` Maximum number of items allowed in a feed. Feed item @@ -263,7 +293,8 @@ Setting name (default value) What does it do? ================================================ ===================================================== If you don't want to generate some of these feeds, set ``None`` to the -variables above. +variables above. If you don't want to generate any feeds set both ``FEED_ATOM`` +and ``FEED_RSS`` to none. .. [2] %s is the name of the category. @@ -274,7 +305,7 @@ If you want to use FeedBurner for your feed, you will likely need to decide upon a unique identifier. For example, if your site were called "Thyme" and hosted on the www.example.com domain, you might use "thymefeeds" as your unique identifier, which we'll use throughout this section for illustrative -purposes. In your Pelican settings, set the `FEED` attribute to +purposes. In your Pelican settings, set the `FEED_ATOM` attribute to "thymefeeds/main.xml" to create an Atom feed with an original address of `http://www.example.com/thymefeeds/main.xml`. Set the `FEED_DOMAIN` attribute to `http://feeds.feedburner.com`, or `http://feeds.example.com` if you are @@ -299,10 +330,10 @@ You can use the following settings to configure the pagination. ================================================ ===================================================== Setting name (default value) What does it do? ================================================ ===================================================== -`DEFAULT_ORPHANS` (0) The minimum number of articles allowed on the +`DEFAULT_ORPHANS` (``0``) The minimum number of articles allowed on the last page. Use this when you don't want to have a last page with very few articles. -`DEFAULT_PAGINATION` (False) The maximum number of articles to include on a +`DEFAULT_PAGINATION` (``False``) The maximum number of articles to include on a page, not including orphans. False to disable pagination. ================================================ ===================================================== @@ -316,9 +347,9 @@ following settings. ================================================ ===================================================== Setting name (default value) What does it do? ================================================ ===================================================== -`TAG_CLOUD_STEPS` (4) Count of different font sizes in the tag +`TAG_CLOUD_STEPS` (``4``) Count of different font sizes in the tag cloud. -`TAG_CLOUD_MAX_ITEMS` (100) Maximum number of tags in the cloud. +`TAG_CLOUD_MAX_ITEMS` (``100``) Maximum number of tags in the cloud. ================================================ ===================================================== The default theme does not support tag clouds, but it is pretty easy to add:: @@ -338,12 +369,13 @@ Translations Pelican offers a way to translate articles. See the Getting Started section for more information. -================================================ ===================================================== -Setting name (default value) What does it do? -================================================ ===================================================== -`DEFAULT_LANG` (``'en'``) The default language to use. -`TRANSLATION_FEED` ('feeds/all-%s.atom.xml'[3]_) Where to put the feed for translations. -================================================ ===================================================== +===================================================== ===================================================== +Setting name (default value) What does it do? +===================================================== ===================================================== +`DEFAULT_LANG` (``'en'``) The default language to use. +`TRANSLATION_FEED_ATOM` ('feeds/all-%s.atom.xml'[3]_) Where to put the Atom feed for translations. +`TRANSLATION_FEED_RSS` (``None``, i.e. no RSS) Where to put the RSS feed for translations. +===================================================== ===================================================== .. [3] %s is the language @@ -353,25 +385,25 @@ Ordering content ================================================ ===================================================== Setting name (default value) What does it do? ================================================ ===================================================== -`REVERSE_ARCHIVE_ORDER` (``False``) Reverse the archives list order. (True: orders by date - in descending order, with newer articles first.) +`NEWEST_FIRST_ARCHIVES` (``True``) Order archives by newest first by date. (False: + orders by date with older articles first.) `REVERSE_CATEGORY_ORDER` (``False``) Reverse the category order. (True: lists by reverse alphabetical order; default lists alphabetically.) ================================================ ===================================================== -Theming -======= +Themes +====== -Theming is addressed in a dedicated section (see :ref:`theming-pelican`). -However, here are the settings that are related to theming. +Creating Pelican themes is addressed in a dedicated section (see :ref:`theming-pelican`). +However, here are the settings that are related to themes. ================================================ ===================================================== Setting name (default value) What does it do? ================================================ ===================================================== -`THEME` Theme to use to produce the output. Can be the - complete static path to a theme folder, or - chosen between the list of default themes (see - below) +`THEME` Theme to use to produce the output. Can be a relative + or absolute path to a theme folder, or the name of a + default theme or a theme installed via + ``pelican-themes`` (see below). `THEME_STATIC_PATHS` (``['static']``) Static theme paths you want to copy. Default value is `static`, but if your theme has other static paths, you can put them here. @@ -379,22 +411,32 @@ Setting name (default value) What does it do? `WEBASSETS` (``False``) Asset management with `webassets` (see below) ================================================ ===================================================== -By default, two themes are available. You can specify them using the `-t` option: + +By default, two themes are available. You can specify them using the `THEME` setting or by passing the +``-t`` option to the ``pelican`` command: * notmyidea -* simple (a synonym for "full text" :) - -You can define your own theme too, and specify its placement in the same -manner. (Be sure to specify the full absolute path to it.) - -Here is :doc:`a guide on how to create your theme ` - -You can find a list of themes at http://github.com/ametaireau/pelican-themes. +* simple (a synonym for "plain text" :) +There are a number of other themes available at http://github.com/getpelican/pelican-themes. Pelican comes with :doc:`pelican-themes`, a small script for managing themes. -The `notmyidea` theme can make good use of the following settings. I recommend -using them in your themes as well. +You can define your own theme, either by starting from scratch or by duplicating +and modifying a pre-existing theme. Here is :doc:`a guide on how to create your theme `. + +Following are example ways to specify your preferred theme:: + + # Specify name of a built-in theme + THEME = "notmyidea" + # Specify name of a theme installed via the pelican-themes tool + THEME = "chunk" + # Specify a customized theme, via path relative to the settings file + THEME = "themes/mycustomtheme" + # Specify a customized theme, via absolute path + THEME = "~/projects/mysite/themes/mycustomtheme" + +The built-in `notmyidea` theme can make good use of the following settings. Feel +free to use them in your themes as well. ======================= ======================================================= Setting name What does it do ? @@ -430,26 +472,27 @@ adding the following to your configuration:: Asset management ---------------- -The `WEBASSETS` setting allows to use the `webassets`_ module to manage assets -(css, js). The module must first be installed:: +The `WEBASSETS` setting allows you to use the `webassets`_ module to manage +assets such as CSS and JS files. The module must first be installed:: pip install webassets -`webassets` allows to concatenate your assets and to use almost all of the -hype tools of the moment (see the `documentation`_): +The `webassets` module allows you to perform a number of useful asset management +functions, including: -* css minifier (`cssmin`, `yuicompressor`, ...) -* css compiler (`less`, `sass`, ...) -* js minifier (`uglifyjs`, `yuicompressor`, `closure`, ...) +* CSS minifier (`cssmin`, `yuicompressor`, ...) +* CSS compiler (`less`, `sass`, ...) +* JS minifier (`uglifyjs`, `yuicompressor`, `closure`, ...) -Others filters include gzip compression, integration of images in css with -`datauri` and more. Webassets also append a version identifier to your asset -url to convince browsers to download new versions of your assets when you use -far future expires headers. +Others filters include gzip compression, integration of images in CSS via data +URIs, and more. `webassets` can also append a version identifier to your asset +URL to convince browsers to download new versions of your assets when you use +far-future expires headers. Please refer to the `webassets documentation`_ for +more information. -When using it with Pelican, `webassets` is configured to process assets in the -``OUTPUT_PATH/theme`` directory. You can use it in your templates with a -template tag, for example: +When using with Pelican, `webassets` is configured to process assets in the +``OUTPUT_PATH/theme`` directory. You can use `webassets` in your templates by +including one or more template tags. For example... .. code-block:: jinja @@ -457,28 +500,43 @@ template tag, for example: {% endassets %} -will produce a minified css file with the version identifier: +... will produce a minified css file with a version identifier: .. code-block:: html -Another example for javascript: +These filters can be combined. Here is an example that uses the SASS compiler +and minifies the output: + +.. code-block:: jinja + + {% assets filters="sass,cssmin", output="css/style.min.css", "css/style.scss" %} + + {% endassets %} + +Another example for Javascript: .. code-block:: jinja {% assets filters="uglifyjs,gzip", output="js/packed.js", "js/jquery.js", "js/base.js", "js/widgets.js" %} - + {% endassets %} -will produce a minified and gzipped js file: +The above will produce a minified and gzipped JS file: .. code-block:: html +Pelican's debug mode is propagated to `webassets` to disable asset packaging +and instead work with the uncompressed assets. However, this also means that +the LESS and SASS files are not compiled. This should be fixed in a future +version of `webassets` (cf. the related `bug report +`_). + .. _webassets: https://github.com/miracle2k/webassets -.. _documentation: http://webassets.readthedocs.org/en/latest/builtin_filters.html +.. _webassets documentation: http://webassets.readthedocs.org/en/latest/builtin_filters.html Example settings ================ diff --git a/docs/themes.rst b/docs/themes.rst index 7b251dc1..d06c97a8 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -3,11 +3,10 @@ How to create themes for Pelican ################################ -Pelican uses the great `jinja2 `_ templating engine to -generate its HTML output. The jinja2 syntax is really simple. If you want to -create your own theme, feel free to take inspiration from the "simple" theme, -which is available `here -`_ +Pelican uses the great `Jinja2 `_ templating engine to +generate its HTML output. Jinja2 syntax is really simple. If you want to +create your own theme, feel free to take inspiration from the `"simple" theme +`_. Structure ========= @@ -30,13 +29,13 @@ To make your own theme, you must follow the following structure:: └── tags.html // must list all the tags. Can be a tag cloud. * `static` contains all the static assets, which will be copied to the output - `theme/static` folder. I've put the CSS and image folders here, but they are + `theme` folder. I've put the CSS and image folders here, but they are just examples. Put what you need here. * `templates` contains all the templates that will be used to generate the content. I've just put the mandatory templates here; you can define your own if it helps you keep things organized while creating your theme. - + Templates and variables ======================= @@ -45,7 +44,7 @@ This document describes which templates should exist in a theme, and which variables will be passed to each template at generation time. All templates will receive the variables defined in your settings file, if they -are in all-caps. You can access them directly. +are in all-caps. You can access them directly. Common variables ---------------- @@ -56,14 +55,14 @@ All of these settings will be available to all templates. Variable Description ============= =================================================== articles The list of articles, ordered descending by date - All the elements are `Article` objects, so you can + All the elements are `Article` objects, so you can access their attributes (e.g. title, summary, author etc.) dates The same list of articles, but ordered by date, ascending tags A key-value dict containing the tags (the keys) and the list of respective articles (the values) -categories A key-value dict containing the categories (keys) +categories A key-value dict containing the categories (keys) and the list of respective articles (values) pages The list of pages ============= =================================================== @@ -183,7 +182,7 @@ dates Articles related to this tag, but ordered by date, ascending articles_paginator A paginator object for the list of articles articles_page The current page of articles -dates_paginator A paginator object for the list of articles, +dates_paginator A paginator object for the list of articles, ordered by date, ascending dates_page The current page of articles, ordered by date, ascending @@ -191,6 +190,23 @@ page_name TAG_URL where everything after `{slug}` is removed -- useful for pagination links =================== =================================================== +Feeds +===== + +The feed variables changed in 3.0. Each variable now explicitly lists ATOM or +RSS in the name. ATOM is still the default. Old themes will need to be updated. +Here is a complete list of the feed variables:: + + FEED_ATOM + FEED_RSS + CATEGORY_FEED_ATOM + CATEGORY_FEED_RSS + TAG_FEED_ATOM + TAG_FEED_RSS + TRANSLATION_FEED_ATOM + TRANSLATION_FEED_RSS + + Inheritance =========== diff --git a/docs/tips.rst b/docs/tips.rst index 14a79a5e..abb739b1 100644 --- a/docs/tips.rst +++ b/docs/tips.rst @@ -12,16 +12,16 @@ file generator, we can take advantage of this. User Pages ---------- -Github allows you to create user pages in the form of ``username.github.com``. -Whatever is created in master branch will be published. For this purposes just -the output generated by pelican needs to pushed at github. +GitHub allows you to create user pages in the form of ``username.github.com``. +Whatever is created in the master branch will be published. For this purpose, +just the output generated by Pelican needs to pushed to GitHub. -So given a repository containing your articles, just run pelican over the posts -and deploy the master branch at github:: +So given a repository containing your articles, just run Pelican over the posts +and deploy the master branch to GitHub:: $ pelican -s pelican.conf.py ./path/to/posts -o /path/to/output -Now add all the files in the output directory generated by pelican:: +Now add all the files in the output directory generated by Pelican:: $ git add /path/to/output/* $ git commit -am "Your Message" @@ -31,12 +31,12 @@ Project Pages ------------- For creating Project pages, a branch called ``gh-pages`` is used for publishing. The excellent `ghp-import `_ makes this -really easy. You will have to install it:: +really easy, which can be installed via:: $ pip install ghp-import -Then, given a repository containing your articles, you would simply have -to run Pelican and upload the output to GitHub:: +Then, given a repository containing your articles, you would simply run +Pelican and upload the output to GitHub:: $ pelican -s pelican.conf.py . $ ghp-import output @@ -45,10 +45,8 @@ to run Pelican and upload the output to GitHub:: And that's it. If you want, you can put that directly into a post-commit hook, so each time you -commit, your blog is up to date on GitHub! +commit, your blog is up-to-date on GitHub! -Put the following into `.git/hooks/post-commit`:: - - pelican -s pelican.conf.py . && ghp-import output && git push origin - gh-pages +Put the following into ``.git/hooks/post-commit``:: + pelican -s pelican.conf.py . && ghp-import output && git push origin gh-pages diff --git a/pelican/__init__.py b/pelican/__init__.py index 6dc7dd36..9809b19b 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -8,10 +8,12 @@ import argparse from pelican import signals from pelican.generators import (ArticlesGenerator, PagesGenerator, - StaticGenerator, PdfGenerator, LessCSSGenerator) + StaticGenerator, PdfGenerator, + LessCSSGenerator, SourceFileGenerator) from pelican.log import init -from pelican.settings import read_settings, _DEFAULT_CONFIG -from pelican.utils import clean_output_dir, files_changed, file_changed +from pelican.settings import read_settings +from pelican.utils import (clean_output_dir, files_changed, file_changed, + NoFilesError) from pelican.writers import Writer __major__ = 3 @@ -23,55 +25,40 @@ logger = logging.getLogger(__name__) class Pelican(object): - def __init__(self, settings=None, path=None, theme=None, output_path=None, - markup=None, delete_outputdir=False, plugin_path=None): - """Read the settings, and performs some checks on the environment - before doing anything else. + def __init__(self, settings): + """ + Pelican initialisation, performs some checks on the environment before + doing anything else. """ - if settings is None: - settings = _DEFAULT_CONFIG - - self.path = path or settings['PATH'] - if not self.path: - raise Exception('You need to specify a path containing the content' - ' (see pelican --help for more information)') - - if self.path.endswith('/'): - self.path = self.path[:-1] # define the default settings self.settings = settings - self._handle_deprecation() - self.theme = theme or settings['THEME'] - output_path = output_path or settings['OUTPUT_PATH'] - self.output_path = os.path.realpath(output_path) - self.markup = markup or settings['MARKUP'] - self.delete_outputdir = delete_outputdir \ - or settings['DELETE_OUTPUT_DIRECTORY'] - - # find the theme in pelican.theme if the given one does not exists - if not os.path.exists(self.theme): - theme_path = os.sep.join([os.path.dirname( - os.path.abspath(__file__)), "themes/%s" % self.theme]) - if os.path.exists(theme_path): - self.theme = theme_path - else: - raise Exception("Impossible to find the theme %s" % theme) + self.path = settings['PATH'] + self.theme = settings['THEME'] + self.output_path = settings['OUTPUT_PATH'] + self.markup = settings['MARKUP'] + self.delete_outputdir = settings['DELETE_OUTPUT_DIRECTORY'] + self.init_path() self.init_plugins() signals.initialized.send(self) + def init_path(self): + if not any(p in sys.path for p in ['', '.']): + logger.debug("Adding current directory to system path") + sys.path.insert(0, '') + def init_plugins(self): self.plugins = self.settings['PLUGINS'] for plugin in self.plugins: # if it's a string, then import it if isinstance(plugin, basestring): - log.debug("Loading plugin `{0}' ...".format(plugin)) + logger.debug("Loading plugin `{0}' ...".format(plugin)) plugin = __import__(plugin, globals(), locals(), 'module') - log.debug("Registering plugin `{0}' ...".format(plugin.__name__)) + logger.debug("Registering plugin `{0}'".format(plugin.__name__)) plugin.register() def _handle_deprecation(self): @@ -114,6 +101,35 @@ class Pelican(object): self.settings[setting]) logger.warning("%s = '%s'" % (setting, self.settings[setting])) + if self.settings.get('FEED', False): + logger.warning('Found deprecated `FEED` in settings. Modify FEED' + ' to FEED_ATOM in your settings and theme for the same behavior.' + ' Temporarily setting FEED_ATOM for backwards compatibility.') + self.settings['FEED_ATOM'] = self.settings['FEED'] + + if self.settings.get('TAG_FEED', False): + logger.warning('Found deprecated `TAG_FEED` in settings. Modify ' + ' TAG_FEED to TAG_FEED_ATOM in your settings and theme for the ' + 'same behavior. Temporarily setting TAG_FEED_ATOM for backwards ' + 'compatibility.') + self.settings['TAG_FEED_ATOM'] = self.settings['TAG_FEED'] + + if self.settings.get('CATEGORY_FEED', False): + logger.warning('Found deprecated `CATEGORY_FEED` in settings. ' + 'Modify CATEGORY_FEED to CATEGORY_FEED_ATOM in your settings and ' + 'theme for the same behavior. Temporarily setting ' + 'CATEGORY_FEED_ATOM for backwards compatibility.') + self.settings['CATEGORY_FEED_ATOM'] =\ + self.settings['CATEGORY_FEED'] + + if self.settings.get('TRANSLATION_FEED', False): + logger.warning('Found deprecated `TRANSLATION_FEED` in settings. ' + 'Modify TRANSLATION_FEED to TRANSLATION_FEED_ATOM in your ' + 'settings and theme for the same behavior. Temporarily setting ' + 'TRANSLATION_FEED_ATOM for backwards compatibility.') + self.settings['TRANSLATION_FEED_ATOM'] =\ + self.settings['TRANSLATION_FEED'] + def run(self): """Run the generators and return""" @@ -151,12 +167,28 @@ class Pelican(object): if hasattr(p, 'generate_output'): p.generate_output(writer) + signals.finalized.send(self) + def get_generator_classes(self): generators = [StaticGenerator, ArticlesGenerator, PagesGenerator] if self.settings['PDF_GENERATOR']: generators.append(PdfGenerator) if self.settings['LESS_GENERATOR']: # can be True or PATH to lessc generators.append(LessCSSGenerator) + if self.settings['OUTPUT_SOURCES']: + generators.append(SourceFileGenerator) + + for pair in signals.get_generators.send(self): + (funct, value) = pair + + if not isinstance(value, (tuple, list)): + value = (value, ) + + for v in value: + if isinstance(v, type): + logger.debug('Found generator: {0}'.format(v)) + generators.append(v) + return generators def get_writer(self): @@ -213,11 +245,26 @@ def parse_arguments(): return parser.parse_args() -def get_instance(args): - markup = [a.strip().lower() for a in args.markup.split(',')]\ - if args.markup else None +def get_config(args): + config = {} + if args.path: + config['PATH'] = os.path.abspath(os.path.expanduser(args.path)) + if args.output: + config['OUTPUT_PATH'] = \ + os.path.abspath(os.path.expanduser(args.output)) + if args.markup: + config['MARKUP'] = [a.strip().lower() for a in args.markup.split(',')] + if args.theme: + abstheme = os.path.abspath(os.path.expanduser(args.theme)) + config['THEME'] = abstheme if os.path.exists(abstheme) else args.theme + if args.delete_outputdir is not None: + config['DELETE_OUTPUT_DIRECTORY'] = args.delete_outputdir + return config - settings = read_settings(args.settings) + +def get_instance(args): + + settings = read_settings(args.settings, override=get_config(args)) cls = settings.get('PELICAN_CLASS') if isinstance(cls, basestring): @@ -225,19 +272,17 @@ def get_instance(args): module = __import__(module) cls = getattr(module, cls_name) - return cls(settings, args.path, args.theme, args.output, markup, - args.delete_outputdir) + return cls(settings) def main(): args = parse_arguments() init(args.verbosity) - # Split the markup languages only if some have been given. Otherwise, - # populate the variable with None. pelican = get_instance(args) try: if args.autoreload: + files_found_error = True while True: try: # Check source dir for changed files ending with the given @@ -247,6 +292,8 @@ def main(): # have. if files_changed(pelican.path, pelican.markup) or \ files_changed(pelican.theme, ['']): + if not files_found_error: + files_found_error = True pelican.run() # reload also if settings.py changed @@ -258,7 +305,19 @@ def main(): time.sleep(.5) # sleep to avoid cpu load except KeyboardInterrupt: + logger.warning("Keyboard interrupt, quitting.") break + except NoFilesError: + if files_found_error: + logger.warning("No valid files found in content. " + "Nothing to generate.") + files_found_error = False + time.sleep(1) # sleep to avoid cpu load + except Exception, e: + logger.warning( + "Caught exception \"{}\". Reloading.".format(e) + ) + continue else: pelican.run() except Exception, e: diff --git a/pelican/contents.py b/pelican/contents.py index ad08d468..0dee19f3 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import copy import locale import logging import functools @@ -10,7 +11,7 @@ from sys import platform, stdin from pelican.settings import _DEFAULT_CONFIG from pelican.utils import slugify, truncate_html_words - +from pelican import signals logger = logging.getLogger(__name__) @@ -21,6 +22,7 @@ class Page(object): :param content: the string to parse, containing the original content. """ mandatory_properties = ('title',) + default_template = 'page' def __init__(self, content, metadata=None, settings=None, filename=None): @@ -28,7 +30,7 @@ class Page(object): if not metadata: metadata = {} if not settings: - settings = _DEFAULT_CONFIG + settings = copy.deepcopy(_DEFAULT_CONFIG) self.settings = settings self._content = content @@ -44,6 +46,9 @@ class Page(object): # also keep track of the metadata attributes available self.metadata = local_metadata + #default template if it's not defined in page + self.template = self._get_template() + # default author to the one in settings if not defined if not hasattr(self, 'author'): if 'AUTHOR' in settings: @@ -101,6 +106,8 @@ class Page(object): if 'summary' in metadata: self._summary = metadata['summary'] + signals.content_object_init.send(self.__class__, instance=self) + def check_properties(self): """test that each mandatory property is set.""" for prop in self.mandatory_properties: @@ -153,9 +160,16 @@ class Page(object): url = property(functools.partial(get_url_setting, key='url')) save_as = property(functools.partial(get_url_setting, key='save_as')) + def _get_template(self): + if hasattr(self, 'template') and self.template is not None: + return self.template + else: + return self.default_template + class Article(Page): mandatory_properties = ('title', 'date', 'category') + default_template = 'article' class Quote(Page): diff --git a/pelican/generators.py b/pelican/generators.py index be88d2b2..fdec93fa 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -6,6 +6,7 @@ import logging import datetime import subprocess +from codecs import open from collections import defaultdict from functools import partial from itertools import chain @@ -16,7 +17,7 @@ from jinja2.exceptions import TemplateNotFound from pelican.contents import Article, Page, Category, is_valid_content from pelican.readers import read_file -from pelican.utils import copy, process_translations, open +from pelican.utils import copy, process_translations from pelican import signals @@ -36,8 +37,11 @@ class Generator(object): # templates cache self._templates = {} - self._templates_path = os.path.expanduser( - os.path.join(self.theme, 'templates')) + self._templates_path = [] + self._templates_path.append(os.path.expanduser( + os.path.join(self.theme, 'templates'))) + self._templates_path += self.settings.get('EXTRA_TEMPLATES_PATHS', []) + theme_path = os.path.dirname(os.path.abspath(__file__)) @@ -116,6 +120,7 @@ class ArticlesGenerator(Generator): self.dates = {} self.tags = defaultdict(list) self.categories = defaultdict(list) + self.related_posts = [] self.authors = defaultdict(list) super(ArticlesGenerator, self).__init__(*args, **kwargs) self.drafts = [] @@ -123,10 +128,17 @@ class ArticlesGenerator(Generator): def generate_feeds(self, writer): """Generate the feeds from the current context, and output files.""" + if self.settings.get('FEED_ATOM') is None \ + and self.settings.get('FEED_RSS') is None: + return + elif self.settings.get('SITEURL') is '': + logger.warning( + 'Feeds generated without SITEURL set properly may not be valid' + ) - if self.settings.get('FEED'): + if self.settings.get('FEED_ATOM'): writer.write_feed(self.articles, self.context, - self.settings['FEED']) + self.settings['FEED_ATOM']) if self.settings.get('FEED_RSS'): writer.write_feed(self.articles, self.context, @@ -134,44 +146,49 @@ class ArticlesGenerator(Generator): for cat, arts in self.categories: arts.sort(key=attrgetter('date'), reverse=True) - if self.settings.get('CATEGORY_FEED'): + if self.settings.get('CATEGORY_FEED_ATOM'): writer.write_feed(arts, self.context, - self.settings['CATEGORY_FEED'] % cat) + self.settings['CATEGORY_FEED_ATOM'] % cat) if self.settings.get('CATEGORY_FEED_RSS'): writer.write_feed(arts, self.context, self.settings['CATEGORY_FEED_RSS'] % cat, feed_type='rss') - if self.settings.get('TAG_FEED') or self.settings.get('TAG_FEED_RSS'): + if self.settings.get('TAG_FEED_ATOM') \ + or self.settings.get('TAG_FEED_RSS'): for tag, arts in self.tags.items(): arts.sort(key=attrgetter('date'), reverse=True) - if self.settings.get('TAG_FEED'): + if self.settings.get('TAG_FEED_ATOM'): writer.write_feed(arts, self.context, - self.settings['TAG_FEED'] % tag) + self.settings['TAG_FEED_ATOM'] % tag) if self.settings.get('TAG_FEED_RSS'): writer.write_feed(arts, self.context, self.settings['TAG_FEED_RSS'] % tag, feed_type='rss') - if self.settings.get('TRANSLATION_FEED'): + if self.settings.get('TRANSLATION_FEED_ATOM') or \ + self.settings.get('TRANSLATION_FEED_RSS'): translations_feeds = defaultdict(list) for article in chain(self.articles, self.translations): translations_feeds[article.lang].append(article) for lang, items in translations_feeds.items(): items.sort(key=attrgetter('date'), reverse=True) - writer.write_feed(items, self.context, - self.settings['TRANSLATION_FEED'] % lang) + if self.settings.get('TRANSLATION_FEED_ATOM'): + writer.write_feed(items, self.context, + self.settings['TRANSLATION_FEED_ATOM'] % lang) + if self.settings.get('TRANSLATION_FEED_RSS'): + writer.write_feed(items, self.context, + self.settings['TRANSLATION_FEED_RSS'] % lang, + feed_type='rss') def generate_articles(self, write): """Generate the articles.""" - article_template = self.get_template('article') for article in chain(self.translations, self.articles): - write(article.save_as, - article_template, self.context, article=article, - category=article.category) + write(article.save_as, self.get_template(article.template), + self.context, article=article, category=article.category) def generate_direct_templates(self, write): """Generate direct templates pages""" @@ -183,7 +200,7 @@ class ArticlesGenerator(Generator): save_as = self.settings.get("%s_SAVE_AS" % template.upper(), '%s.html' % template) if not save_as: - continue + continue write(save_as, self.get_template(template), self.context, blog=True, paginated=paginated, @@ -222,10 +239,10 @@ class ArticlesGenerator(Generator): def generate_drafts(self, write): """Generate drafts pages.""" - article_template = self.get_template('article') for article in self.drafts: - write('drafts/%s.html' % article.slug, article_template, - self.context, article=article, category=article.category) + write('drafts/%s.html' % article.slug, + self.get_template(article.template), self.context, + article=article, category=article.category) def generate_pages(self, writer): """Generate the pages on the disk""" @@ -264,7 +281,7 @@ class ArticlesGenerator(Generator): if 'category' not in metadata: if os.path.dirname(f) == article_path: # if the article is not in a subdirectory - category = self.settings['DEFAULT_CATEGORY'] + category = self.settings['DEFAULT_CATEGORY'] else: category = os.path.basename(os.path.dirname(f))\ .decode('utf-8') @@ -272,9 +289,13 @@ class ArticlesGenerator(Generator): if category != '': metadata['category'] = Category(category, self.settings) - if 'date' not in metadata and self.settings['FALLBACK_ON_FS_DATE']: + if 'date' not in metadata and self.settings['DEFAULT_DATE']: + if self.settings['DEFAULT_DATE'] == 'fs': metadata['date'] = datetime.datetime.fromtimestamp( - os.stat(f).st_ctime) + os.stat(f).st_ctime) + else: + metadata['date'] = datetime.datetime( + *self.settings['DEFAULT_DATE']) signals.article_generate_context.send(self, metadata=metadata) article = Article(content, metadata, settings=self.settings, @@ -305,7 +326,7 @@ class ArticlesGenerator(Generator): self.articles.sort(key=attrgetter('date'), reverse=True) self.dates = list(self.articles) self.dates.sort(key=attrgetter('date'), - reverse=self.context['REVERSE_ARCHIVE_ORDER']) + reverse=self.context['NEWEST_FIRST_ARCHIVES']) # create tag cloud tag_cloud = defaultdict(int) @@ -345,7 +366,9 @@ class ArticlesGenerator(Generator): self.authors.sort(key=lambda item: item[0].name) self._update_context(('articles', 'dates', 'tags', 'categories', - 'tag_cloud', 'authors')) + 'tag_cloud', 'authors', 'related_posts')) + + signals.article_generator_finalized.send(self) def generate_output(self, writer): self.generate_feeds(writer) @@ -357,32 +380,46 @@ class PagesGenerator(Generator): def __init__(self, *args, **kwargs): self.pages = [] + self.hidden_pages = [] + self.hidden_translations = [] super(PagesGenerator, self).__init__(*args, **kwargs) + signals.pages_generator_init.send(self) def generate_context(self): all_pages = [] + hidden_pages = [] for f in self.get_files( os.path.join(self.path, self.settings['PAGE_DIR']), exclude=self.settings['PAGE_EXCLUDES']): try: - content, metadata = read_file(f) + content, metadata = read_file(f, settings=self.settings) except Exception, e: - logger.error(u'Could not process %s\n%s' % (f, str(e))) + logger.warning(u'Could not process %s\n%s' % (f, str(e))) continue + signals.pages_generate_context.send(self, metadata=metadata ) page = Page(content, metadata, settings=self.settings, filename=f) if not is_valid_content(page, f): continue - all_pages.append(page) + if page.status == "published": + all_pages.append(page) + elif page.status == "hidden": + hidden_pages.append(page) + else: + logger.warning(u"Unknown status %s for file %s, skipping it." % + (repr(unicode.encode(page.status, 'utf-8')), + repr(f))) self.pages, self.translations = process_translations(all_pages) + self.hidden_pages, self.hidden_translations = process_translations(hidden_pages) self._update_context(('pages', )) self.context['PAGES'] = self.pages def generate_output(self, writer): - for page in chain(self.translations, self.pages): - writer.write_file(page.save_as, self.get_template('page'), + for page in chain(self.translations, self.pages, + self.hidden_translations, self.hidden_pages): + writer.write_file(page.save_as, self.get_template(page.template), self.context, page=page, relative_urls=self.settings.get('RELATIVE_URLS')) @@ -406,7 +443,23 @@ class StaticGenerator(Generator): # Define the assets environment that will be passed to the # generators. The StaticGenerator must then be run first to have # the assets in the output_path before generating the templates. - assets_url = self.settings['SITEURL'] + '/theme/' + + # Let ASSET_URL honor Pelican's RELATIVE_URLS setting. + # Hint for templates: + # Current version of webassets seem to remove any relative + # paths at the beginning of the URL. So, if RELATIVE_URLS + # is on, ASSET_URL will start with 'theme/', regardless if we + # set assets_url here to './theme/' or to 'theme/'. + # XXX However, this breaks the ASSET_URL if user navigates to + # a sub-URL, e.g. if he clicks on a category. To workaround this + # issue, I use + # + # instead of + # + if self.settings.get('RELATIVE_URLS'): + assets_url = './theme/' + else: + assets_url = self.settings['SITEURL'] + '/theme/' assets_src = os.path.join(self.output_path, 'theme') self.assets_env = AssetsEnvironment(assets_src, assets_url) @@ -430,13 +483,20 @@ class PdfGenerator(Generator): """Generate PDFs on the output dir, for all articles and pages coming from rst""" def __init__(self, *args, **kwargs): + super(PdfGenerator, self).__init__(*args, **kwargs) try: from rst2pdf.createpdf import RstToPdf + pdf_style_path = os.path.join(self.settings['PDF_STYLE_PATH']) \ + if 'PDF_STYLE_PATH' in self.settings.keys() \ + else '' + pdf_style = self.settings['PDF_STYLE'] if 'PDF_STYLE' \ + in self.settings.keys() \ + else 'twelvepoint' self.pdfcreator = RstToPdf(breakside=0, - stylesheets=['twelvepoint']) + stylesheets=[pdf_style], + style_path=[pdf_style_path]) except ImportError: raise Exception("unable to find rst2pdf") - super(PdfGenerator, self).__init__(*args, **kwargs) def _create_pdf(self, obj, output_path): if obj.filename.endswith(".rst"): @@ -444,7 +504,7 @@ class PdfGenerator(Generator): output_pdf = os.path.join(output_path, filename) # print "Generating pdf for", obj.filename, " in ", output_pdf with open(obj.filename) as f: - self.pdfcreator.createPdf(text=f, output=output_pdf) + self.pdfcreator.createPdf(text=f.read(), output=output_pdf) logger.info(u' [ok] writing %s' % output_pdf) def generate_context(self): @@ -468,6 +528,19 @@ class PdfGenerator(Generator): for page in self.context['pages']: self._create_pdf(page, pdf_path) +class SourceFileGenerator(Generator): + def generate_context(self): + self.output_extension = self.settings['OUTPUT_SOURCES_EXTENSION'] + + def _create_source(self, obj, output_path): + filename = os.path.splitext(obj.save_as)[0] + dest = os.path.join(output_path, filename + self.output_extension) + copy('', obj.filename, dest) + + def generate_output(self, writer=None): + logger.info(u' Generating source files...') + for object in chain(self.context['articles'], self.context['pages']): + self._create_source(object, self.output_path) class LessCSSGenerator(Generator): """Compile less css files.""" diff --git a/pelican/plugins/global_license.py b/pelican/plugins/global_license.py index 463a93b3..9a0f5206 100644 --- a/pelican/plugins/global_license.py +++ b/pelican/plugins/global_license.py @@ -4,13 +4,14 @@ from pelican import signals License plugin for Pelican ========================== -Simply add license variable in article's context, which contain -the license text. +This plugin allows you to define a LICENSE setting and adds the contents of that +license variable to the article's context, making that variable available to use +from within your theme's templates. Settings: --------- -Add LICENSE to your settings file to define default license. +Define LICENSE in your settings file with the contents of your default license. """ diff --git a/pelican/plugins/gravatar.py b/pelican/plugins/gravatar.py index 4ab8ea9c..a4d11456 100644 --- a/pelican/plugins/gravatar.py +++ b/pelican/plugins/gravatar.py @@ -5,20 +5,22 @@ from pelican import signals Gravatar plugin for Pelican =========================== -Simply add author_gravatar variable in article's context, which contains -the gravatar url. +This plugin assigns the ``author_gravatar`` variable to the Gravatar URL and +makes the variable available within the article's context. Settings: --------- -Add AUTHOR_EMAIL to your settings file to define default author email. +Add AUTHOR_EMAIL to your settings file to define the default author's email +address. Obviously, that email address must be associated with a Gravatar +account. Article metadata: ------------------ :email: article's author email -If one of them are defined, the author_gravatar variable is added to +If one of them are defined, the author_gravatar variable is added to the article's context. """ diff --git a/pelican/plugins/multi_part.py b/pelican/plugins/multi_part.py new file mode 100644 index 00000000..0581b501 --- /dev/null +++ b/pelican/plugins/multi_part.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +""" +Copyright (c) FELD Boris + +Multiple part support +===================== + +Create a navigation menu for multi-part related_posts + +Article metadata: +------------------ + +:parts: a unique identifier for multi-part posts, must be the same in each +post part. + +Usage +----- + {% if article.metadata.parts_articles %} +
    + {% for part_article in article.metadata.parts_articles %} + {% if part_article == article %} +
  1. + {{ part_article.title }} + +
  2. + {% else %} +
  3. + {{ part_article.title }} + +
  4. + {% endif %} + {% endfor %} +
+ {% endif %} +""" +from collections import defaultdict + +from pelican import signals + + +def aggregate_multi_part(generator): + multi_part = defaultdict(list) + + for article in generator.articles: + if 'parts' in article.metadata: + multi_part[article.metadata['parts']].append(article) + + for part_id in multi_part: + parts = multi_part[part_id] + + # Sort by date + parts.sort(key=lambda x: x.metadata['date']) + + for article in parts: + article.metadata['parts_articles'] = parts + + +def register(): + signals.article_generator_finalized.connect(aggregate_multi_part) diff --git a/pelican/plugins/related_posts.py b/pelican/plugins/related_posts.py new file mode 100644 index 00000000..67715023 --- /dev/null +++ b/pelican/plugins/related_posts.py @@ -0,0 +1,52 @@ +from pelican import signals + +""" +Related posts plugin for Pelican +================================ + +Adds related_posts variable to article's context + +Settings +-------- +To enable, add + + from pelican.plugins import related_posts + PLUGINS = [related_posts] + +to your settings.py. + +Usage +----- + {% if article.related_posts %} +
    + {% for related_post in article.related_posts %} +
  • {{ related_post }}
  • + {% endfor %} +
+ {% endif %} + + +""" + +related_posts = [] + + +def add_related_posts(generator, metadata): + if 'tags' in metadata: + for tag in metadata['tags']: + #print tag + for related_article in generator.tags[tag]: + related_posts.append(related_article) + + if len(related_posts) < 1: + return + + relation_score = dict(zip(set(related_posts), map(related_posts.count, + set(related_posts)))) + ranked_related = sorted(relation_score, key=relation_score.get) + + metadata["related_posts"] = ranked_related[:5] + + +def register(): + signals.article_generate_context.connect(add_related_posts) diff --git a/pelican/plugins/sitemap.py b/pelican/plugins/sitemap.py new file mode 100644 index 00000000..ebce1f04 --- /dev/null +++ b/pelican/plugins/sitemap.py @@ -0,0 +1,190 @@ +import collections +import os.path + +from datetime import datetime +from logging import warning, info +from codecs import open + +from pelican import signals, contents + +TXT_HEADER = u"""{0}/index.html +{0}/archives.html +{0}/tags.html +{0}/categories.html +""" + +XML_HEADER = u""" + +""" + +XML_URL = u""" + +{0}/{1} +{2} +{3} +{4} + +""" + +XML_FOOTER = u""" + +""" + + +def format_date(date): + if date.tzinfo: + tz = date.strftime('%s') + tz = tz[:-2] + ':' + tz[-2:] + else: + tz = "-00:00" + return date.strftime("%Y-%m-%dT%H:%M:%S") + tz + + +class SitemapGenerator(object): + + def __init__(self, context, settings, path, theme, output_path, *null): + + self.output_path = output_path + self.context = context + self.now = datetime.now() + self.siteurl = settings.get('SITEURL') + + self.format = 'xml' + + self.changefreqs = { + 'articles': 'monthly', + 'indexes': 'daily', + 'pages': 'monthly' + } + + self.priorities = { + 'articles': 0.5, + 'indexes': 0.5, + 'pages': 0.5 + } + + config = settings.get('SITEMAP', {}) + + if not isinstance(config, dict): + warning("sitemap plugin: the SITEMAP setting must be a dict") + else: + fmt = config.get('format') + pris = config.get('priorities') + chfreqs = config.get('changefreqs') + + if fmt not in ('xml', 'txt'): + warning("sitemap plugin: SITEMAP['format'] must be `txt' or `xml'") + warning("sitemap plugin: Setting SITEMAP['format'] on `xml'") + elif fmt == 'txt': + self.format = fmt + return + + valid_keys = ('articles', 'indexes', 'pages') + valid_chfreqs = ('always', 'hourly', 'daily', 'weekly', 'monthly', + 'yearly', 'never') + + if isinstance(pris, dict): + for k, v in pris.iteritems(): + if k in valid_keys and not isinstance(v, (int, float)): + default = self.priorities[k] + warning("sitemap plugin: priorities must be numbers") + warning("sitemap plugin: setting SITEMAP['priorities']" + "['{0}'] on {1}".format(k, default)) + pris[k] = default + self.priorities.update(pris) + elif pris is not None: + warning("sitemap plugin: SITEMAP['priorities'] must be a dict") + warning("sitemap plugin: using the default values") + + if isinstance(chfreqs, dict): + for k, v in chfreqs.iteritems(): + if k in valid_keys and v not in valid_chfreqs: + default = self.changefreqs[k] + warning("sitemap plugin: invalid changefreq `{0}'".format(v)) + warning("sitemap plugin: setting SITEMAP['changefreqs']" + "['{0}'] on '{1}'".format(k, default)) + chfreqs[k] = default + self.changefreqs.update(chfreqs) + elif chfreqs is not None: + warning("sitemap plugin: SITEMAP['changefreqs'] must be a dict") + warning("sitemap plugin: using the default values") + + + + def write_url(self, page, fd): + + if getattr(page, 'status', 'published') != 'published': + return + + page_path = os.path.join(self.output_path, page.url) + if not os.path.exists(page_path): + return + + lastmod = format_date(getattr(page, 'date', self.now)) + + if isinstance(page, contents.Article): + pri = self.priorities['articles'] + chfreq = self.changefreqs['articles'] + elif isinstance(page, contents.Page): + pri = self.priorities['pages'] + chfreq = self.changefreqs['pages'] + else: + pri = self.priorities['indexes'] + chfreq = self.changefreqs['indexes'] + + + if self.format == 'xml': + fd.write(XML_URL.format(self.siteurl, page.url, lastmod, chfreq, pri)) + else: + fd.write(self.siteurl + '/' + loc + '\n') + + + def generate_output(self, writer): + path = os.path.join(self.output_path, 'sitemap.{0}'.format(self.format)) + + pages = self.context['pages'] + self.context['articles'] \ + + [ c for (c, a) in self.context['categories']] \ + + [ t for (t, a) in self.context['tags']] \ + + [ a for (a, b) in self.context['authors']] + + for article in self.context['articles']: + pages += article.translations + + info('writing {0}'.format(path)) + + with open(path, 'w', encoding='utf-8') as fd: + + if self.format == 'xml': + fd.write(XML_HEADER) + else: + fd.write(TXT_HEADER.format(self.siteurl)) + + FakePage = collections.namedtuple('FakePage', + ['status', + 'date', + 'url']) + + for standard_page_url in ['index.html', + 'archives.html', + 'tags.html', + 'categories.html']: + fake = FakePage(status='published', + date=self.now, + url=standard_page_url) + self.write_url(fake, fd) + + for page in pages: + self.write_url(page, fd) + + if self.format == 'xml': + fd.write(XML_FOOTER) + + +def get_generators(generators): + return SitemapGenerator + + +def register(): + signals.get_generators.connect(get_generators) diff --git a/pelican/readers.py b/pelican/readers.py index 83565918..dab829b9 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -16,7 +16,7 @@ except ImportError: import re from pelican.contents import Category, Tag, Author -from pelican.utils import get_date, open +from pelican.utils import get_date, pelican_open _METADATA_PROCESSORS = { @@ -63,6 +63,18 @@ def render_node_to_html(document, node): return visitor.astext() +class PelicanHTMLTranslator(HTMLTranslator): + + def visit_abbreviation(self, node): + attrs = {} + if node.hasattr('explanation'): + attrs['title'] = node['explanation'] + self.body.append(self.starttag(node, 'abbr', '', **attrs)) + + def depart_abbreviation(self, node): + self.body.append('') + + class RstReader(Reader): enabled = bool(docutils) file_extensions = ['rst'] @@ -90,8 +102,9 @@ class RstReader(Reader): def _get_publisher(self, filename): extra_params = {'initial_header_level': '2'} pub = docutils.core.Publisher( - destination_class=docutils.io.StringOutput) + destination_class=docutils.io.StringOutput) pub.set_components('standalone', 'restructuredtext', 'html') + pub.writer.translator_class = PelicanHTMLTranslator pub.process_programmatic_settings(None, extra_params, None) pub.set_source(source_path=filename) pub.publish() @@ -116,8 +129,13 @@ class MarkdownReader(Reader): def read(self, filename): """Parse content and metadata of markdown files""" - text = open(filename) - md = Markdown(extensions=set(self.extensions + ['meta'])) + markdown_extensions = self.settings.get('MARKDOWN_EXTENSIONS', []) + if isinstance(markdown_extensions, (str, unicode)): + markdown_extensions = [m.strip() for m in + markdown_extensions.split(',')] + text = pelican_open(filename) + md = Markdown(extensions=set( + self.extensions + markdown_extensions + ['meta'])) content = md.convert(text) metadata = {} @@ -133,7 +151,7 @@ class HtmlReader(Reader): def read(self, filename): """Parse content and metadata of (x)HTML files""" - with open(filename) as content: + with pelican_open(filename) as content: metadata = {'title': 'unnamed'} for i in self._re.findall(content): key = i.split(':')[0][5:].strip() @@ -172,8 +190,8 @@ def read_file(filename, fmt=None, settings=None): # eventually filter the content with typogrify if asked so if settings and settings['TYPOGRIFY']: - from typogrify import Typogrify - content = Typogrify.typogrify(content) - metadata['title'] = Typogrify.typogrify(metadata['title']) + from typogrify.filters import typogrify + content = typogrify(content) + metadata['title'] = typogrify(metadata['title']) return content, metadata diff --git a/pelican/rstdirectives.py b/pelican/rstdirectives.py index 9c821310..b1f1242c 100644 --- a/pelican/rstdirectives.py +++ b/pelican/rstdirectives.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- -from docutils import nodes -from docutils.parsers.rst import directives, Directive +from docutils import nodes, utils +from docutils.parsers.rst import directives, roles, Directive from pygments.formatters import HtmlFormatter from pygments import highlight from pygments.lexers import get_lexer_by_name, TextLexer +import re INLINESTYLES = False DEFAULT = HtmlFormatter(noclasses=INLINESTYLES) @@ -94,3 +95,18 @@ class YouTube(Directive): nodes.raw('', '', format='html')] directives.register_directive('youtube', YouTube) + +_abbr_re = re.compile('\((.*)\)$') + +class abbreviation(nodes.Inline, nodes.TextElement): pass + +def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): + text = utils.unescape(text) + m = _abbr_re.search(text) + if m is None: + return [abbreviation(text, text)], [] + abbr = text[:m.start()].strip() + expl = m.group(1) + return [abbreviation(abbr, abbr, explanation=expl)], [] + +roles.register_local_role('abbr', abbr_role) diff --git a/pelican/settings.py b/pelican/settings.py index 17efea58..4d1ed81e 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +import copy +import imp +import inspect import os import locale import logging @@ -21,18 +24,21 @@ _DEFAULT_CONFIG = {'PATH': '.', 'MARKUP': ('rst', 'md'), 'STATIC_PATHS': ['images', ], 'THEME_STATIC_PATHS': ['static', ], - 'FEED': 'feeds/all.atom.xml', - 'CATEGORY_FEED': 'feeds/%s.atom.xml', - 'TRANSLATION_FEED': 'feeds/all-%s.atom.xml', + 'FEED_ATOM': 'feeds/all.atom.xml', + 'CATEGORY_FEED_ATOM': 'feeds/%s.atom.xml', + 'TRANSLATION_FEED_ATOM': 'feeds/all-%s.atom.xml', 'FEED_MAX_ITEMS': '', + 'SITEURL': '', 'SITENAME': 'A Pelican Blog', 'DISPLAY_PAGES_ON_MENU': True, 'PDF_GENERATOR': False, + 'OUTPUT_SOURCES': False, + 'OUTPUT_SOURCES_EXTENSION': '.text', 'DEFAULT_CATEGORY': 'misc', - 'FALLBACK_ON_FS_DATE': True, + 'DEFAULT_DATE': 'fs', 'WITH_FUTURE_DATES': True, 'CSS_FILE': 'main.css', - 'REVERSE_ARCHIVE_ORDER': False, + 'NEWEST_FIRST_ARCHIVES': True, 'REVERSE_CATEGORY_ORDER': False, 'DELETE_OUTPUT_DIRECTORY': False, 'ARTICLE_URL': '{slug}.html', @@ -54,6 +60,7 @@ _DEFAULT_CONFIG = {'PATH': '.', 'TAG_CLOUD_STEPS': 4, 'TAG_CLOUD_MAX_ITEMS': 100, 'DIRECT_TEMPLATES': ('index', 'tags', 'categories', 'archives'), + 'EXTRA_TEMPLATES_PATHS' : [], 'PAGINATED_DIRECT_TEMPLATES': ('index', ), 'PELICAN_CLASS': 'pelican.Pelican', 'DEFAULT_DATE_FORMAT': '%a %d %B %Y', @@ -71,46 +78,70 @@ _DEFAULT_CONFIG = {'PATH': '.', 'SUMMARY_MAX_LENGTH': 50, 'WEBASSETS': False, 'PLUGINS': [], + 'MARKDOWN_EXTENSIONS': ['toc', ], } -def read_settings(filename=None): +def read_settings(filename=None, override=None): if filename: local_settings = get_settings_from_file(filename) + # Make the paths relative to the settings file + for p in ['PATH', 'OUTPUT_PATH', 'THEME']: + if p in local_settings and local_settings[p] is not None \ + and not isabs(local_settings[p]): + absp = os.path.abspath(os.path.normpath(os.path.join( + os.path.dirname(filename), local_settings[p]))) + if p != 'THEME' or os.path.exists(p): + local_settings[p] = absp else: - local_settings = _DEFAULT_CONFIG - configured_settings = configure_settings(local_settings, None, filename) - return configured_settings + local_settings = copy.deepcopy(_DEFAULT_CONFIG) + + if override: + local_settings.update(override) + + return configure_settings(local_settings) -def get_settings_from_file(filename, default_settings=None): - """Load a Python file into a dictionary. +def get_settings_from_module(module=None, default_settings=_DEFAULT_CONFIG): """ - if default_settings == None: - default_settings = _DEFAULT_CONFIG - context = default_settings.copy() - if filename: - tempdict = {} - execfile(filename, tempdict) - for key in tempdict: - if key.isupper(): - context[key] = tempdict[key] + Load settings from a module, returning a dict. + """ + + context = copy.deepcopy(default_settings) + if module is not None: + context.update( + (k, v) for k, v in inspect.getmembers(module) if k.isupper()) return context -def configure_settings(settings, default_settings=None, filename=None): - """Provide optimizations, error checking, and warnings for loaded settings""" - if default_settings is None: - default_settings = _DEFAULT_CONFIG +def get_settings_from_file(filename, default_settings=_DEFAULT_CONFIG): + """ + Load settings from a file path, returning a dict. - # Make the paths relative to the settings file - if filename: - for path in ['PATH', 'OUTPUT_PATH']: - if path in settings: - if settings[path] is not None and not isabs(settings[path]): - settings[path] = os.path.abspath(os.path.normpath( - os.path.join(os.path.dirname(filename), settings[path])) - ) + """ + + name = os.path.basename(filename).rpartition(".")[0] + module = imp.load_source(name, filename) + return get_settings_from_module(module, default_settings=default_settings) + + +def configure_settings(settings): + """ + Provide optimizations, error checking, and warnings for loaded settings + """ + if not 'PATH' in settings or not os.path.isdir(settings['PATH']): + raise Exception('You need to specify a path containing the content' + ' (see pelican --help for more information)') + + # find the theme in pelican.theme if the given one does not exists + if not os.path.isdir(settings['THEME']): + theme_path = os.sep.join([os.path.dirname( + os.path.abspath(__file__)), "themes/%s" % settings['THEME']]) + if os.path.exists(theme_path): + settings['THEME'] = theme_path + else: + raise Exception("Impossible to find the theme %s" + % settings['THEME']) # if locales is not a list, make it one locales = settings['LOCALE'] @@ -125,7 +156,7 @@ def configure_settings(settings, default_settings=None, filename=None): for locale_ in locales: try: locale.setlocale(locale.LC_ALL, locale_) - break # break if it is successfull + break # break if it is successful except locale.Error: pass else: @@ -142,7 +173,7 @@ def configure_settings(settings, default_settings=None, filename=None): settings['FEED_DOMAIN'] = settings['SITEURL'] # Warn if feeds are generated with both SITEURL & FEED_DOMAIN undefined - if (('FEED' in settings) or ('FEED_RSS' in settings)) and (not 'FEED_DOMAIN' in settings): + if (('FEED_ATOM' in settings) or ('FEED_RSS' in settings)) and (not 'FEED_DOMAIN' in settings): logger.warn("Since feed URLs should always be absolute, you should specify " "FEED_DOMAIN in your settings. (e.g., 'FEED_DOMAIN = " "http://www.example.com')") @@ -161,4 +192,11 @@ def configure_settings(settings, default_settings=None, filename=None): logger.warn("You must install the webassets module to use WEBASSETS.") settings['WEBASSETS'] = False + if 'OUTPUT_SOURCES_EXTENSION' in settings: + if not isinstance(settings['OUTPUT_SOURCES_EXTENSION'], str): + settings['OUTPUT_SOURCES_EXTENSION'] = _DEFAULT_CONFIG['OUTPUT_SOURCES_EXTENSION'] + logger.warn("Detected misconfiguration with OUTPUT_SOURCES_EXTENSION." + " falling back to the default extension " + + _DEFAULT_CONFIG['OUTPUT_SOURCES_EXTENSION']) + return settings diff --git a/pelican/signals.py b/pelican/signals.py index b1c35794..73e718b5 100644 --- a/pelican/signals.py +++ b/pelican/signals.py @@ -1,5 +1,11 @@ from blinker import signal initialized = signal('pelican_initialized') +finalized = signal('pelican_finalized') article_generate_context = signal('article_generate_context') article_generator_init = signal('article_generator_init') +article_generator_finalized = signal('article_generate_finalized') +get_generators = signal('get_generators') +pages_generate_context = signal('pages_generate_context') +pages_generator_init = signal('pages_generator_init') +content_object_init = signal('content_object_init') diff --git a/pelican/themes/notmyidea/static/css/main.css b/pelican/themes/notmyidea/static/css/main.css index dce9e247..3d94200b 100644 --- a/pelican/themes/notmyidea/static/css/main.css +++ b/pelican/themes/notmyidea/static/css/main.css @@ -70,9 +70,6 @@ p {margin-bottom: 1.143em;} strong, b {font-weight: bold;} em, i {font-style: italic;} -::-moz-selection {background: #F6CF74; color: #fff;} -::selection {background: #F6CF74; color: #fff;} - /* Lists */ ul { list-style: outside disc; @@ -100,7 +97,7 @@ dl {margin: 0 0 1.5em 0;} dt {font-weight: bold;} dd {margin-left: 1.5em;} -pre{background-color: #000; padding: 10px; color: #fff; margin: 10px; overflow: auto;} +pre{background-color: rgb(238, 238, 238); padding: 10px; margin: 10px; overflow: auto;} /* Quotes */ blockquote { @@ -144,8 +141,8 @@ aside, nav, article, figure { /***** Layout *****/ .body {clear: both; margin: 0 auto; width: 800px;} -img.right figure.right {float: right; margin: 0 0 2em 2em;} -img.left, figure.left {float: right; margin: 0 0 2em 2em;} +img.right, figure.right {float: right; margin: 0 0 2em 2em;} +img.left, figure.left {float: left; margin: 0 2em 2em 0;} /* Header @@ -163,7 +160,6 @@ img.left, figure.left {float: right; margin: 0 0 2em 2em;} font-weight: bold; margin: 0 0 .6em .2em; text-decoration: none; - width: 427px; } #banner h1 a:hover, #banner h1 a:active { background: none; @@ -312,7 +308,8 @@ img.left, figure.left {float: right; margin: 0 0 2em 2em;} .social a[type$='atom+xml'], .social a[type$='rss+xml'] {background-image: url('../images/icons/rss.png');} .social a[href*='twitter.com'] {background-image: url('../images/icons/twitter.png');} .social a[href*='linkedin.com'] {background-image: url('../images/icons/linkedin.png');} - .social a[href*='gitorious.org'] {background-image: url('../images/icons/gitorious.org');} + .social a[href*='gitorious.org'] {background-image: url('../images/icons/gitorious.png');} + .social a[href*='gittip.com'] {background-image: url('../images/icons/gittip.png');} /* About diff --git a/pelican/themes/notmyidea/static/css/pygment.css b/pelican/themes/notmyidea/static/css/pygment.css index 594b0fa3..fdd056f6 100644 --- a/pelican/themes/notmyidea/static/css/pygment.css +++ b/pelican/themes/notmyidea/static/css/pygment.css @@ -1,5 +1,5 @@ .hll { -background-color:#FFFFCC; +background-color:#eee; } .c { color:#408090; diff --git a/pelican/themes/notmyidea/static/images/icons/gittip.png b/pelican/themes/notmyidea/static/images/icons/gittip.png new file mode 100644 index 00000000..bb12a139 Binary files /dev/null and b/pelican/themes/notmyidea/static/images/icons/gittip.png differ diff --git a/pelican/themes/notmyidea/templates/analytics.html b/pelican/themes/notmyidea/templates/analytics.html index ba174fcc..4de2c86b 100644 --- a/pelican/themes/notmyidea/templates/analytics.html +++ b/pelican/themes/notmyidea/templates/analytics.html @@ -1,11 +1,12 @@ {% if GOOGLE_ANALYTICS %} - {% endif %} \ No newline at end of file diff --git a/pelican/themes/notmyidea/templates/base.html b/pelican/themes/notmyidea/templates/base.html index c9f2c0c8..6ab87c5a 100644 --- a/pelican/themes/notmyidea/templates/base.html +++ b/pelican/themes/notmyidea/templates/base.html @@ -4,7 +4,9 @@ {% block title %}{{ SITENAME }}{%endblock%} - + {% if FEED_ATOM %} + + {% endif %} {% if FEED_RSS %} {% endif %} @@ -56,7 +58,7 @@ - - - + + + @@ -139,7 +142,7 @@ as well as inline markup.