Merge branch 'master' into better_figures_and_images

This commit is contained in:
Duncan Lock
2013-06-18 00:57:25 -07:00
18 changed files with 535 additions and 14 deletions
+8 -7
View File
@@ -3,7 +3,7 @@ Contributing a plugin
Details of how to write a plugin is explained in the official Pelican `docs`_.
If you want to contribute, please fork this repository and issue your pull
If you want to contribute, please fork this repository and issue your pull
request. Make sure that your plugin follows the structure below::
my_plugin
@@ -11,7 +11,7 @@ request. Make sure that your plugin follows the structure below::
├── my_plugin.py
├── test_my_plugin.py
└── Readme.rst / Readme.md
``my_plugin.py`` is the actual plugin implementation. Include a brief
explanation of what the plugin does as a module docstring. Leave any further
@@ -19,12 +19,13 @@ explanations and usage details to ``Readme`` file.
``__init__.py`` should contain a single line with ``from .my_plugin import *``.
Place tests for your plugin in the same folder with name ``test_my_plugin.py``.
Place tests for your plugin in the same folder with name ``test_my_plugin.py``.
You can use ``test_data`` main folder, if you need content or templates in your tests.
**Note:** Plugins in the repository are licensed with *GNU AFFERO GENERAL PUBLIC LICENSE
Version 3*. By submitting a pull request, you accept to release your
contribution under same license. Please refer to the ``LICENSE`` file for
full text of the license.
**Note:** Each plugin can contain a LICENSE file stating the license it's
released under. If there is an absence of LICENSE then it defaults to the
*GNU AFFERO GENERAL PUBLIC LICENSE Version 3*.
Please refer to the ``LICENSE`` file for the full text of the license.
.. _docs: http://docs.getpelican.com/en/latest/plugins.html#how-to-create-plugins
+4
View File
@@ -1,3 +1,7 @@
Unless the folder itself contains a LICENSE stating otherwise, all the files
distributed here are released under the GNU AFFERO GENERAL PUBLIC LICENSE.
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
+69
View File
@@ -0,0 +1,69 @@
Include Pygments highlighted code with reStructuredText
=======================================================
:author: Colin Dunklau
Use this plugin to make writing coding tutorials easier! You can
maintain the example source files separately from the actual article.
Based heavily on ``docutils.parsers.rst.directives.Include``. Include
a file and output as a code block formatted with pelican's Pygments
directive.
Note that this is broken with the Docutils 0.10 release, there's a
circular import. It was fixed in trunk:
http://sourceforge.net/p/docutils/bugs/214/
Directives
----------
.. code:: rst
.. code-include:: incfile.py
:lexer: string, name of the Pygments lexer to use, default 'text'
:encoding: string, encoding with which to open the file
:tab-width: integer, hard tabs are replaced with `tab-width` spaces
:start-line: integer, starting line to begin reading include file
:end-line: integer, last line from include file to display
``start-line``, and ``end-line`` have the same meaning as in the
docutils ``include`` directive, that is, they index from zero.
Example
-------
./incfile.py:
.. code:: python
# These two comment lines will not
# be included in the output
import random
insults = ['I fart in your general direction',
'your mother was a hampster',
'your father smelt of elderberries']
def insult():
print random.choice(insults)
# This comment line will be included
# ...but this one won't
./yourfile.rst:
.. code:: rst
How to Insult the English
=========================
:author: Pierre Devereaux
A function to help insult those silly English knnnnnnniggets:
.. code-include:: incfile.py
:lexer: python
:encoding: utf-8
:tab-width: 4
:start-line: 3
:end-line: 11
+1
View File
@@ -0,0 +1 @@
from code_include import *
+87
View File
@@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os.path
from docutils import io, nodes, statemachine, utils
from docutils.utils.error_reporting import SafeString, ErrorString
from docutils.parsers.rst import directives, Directive
from pelican.rstdirectives import Pygments
class CodeInclude(Directive):
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = {'lexer': directives.unchanged,
'encoding': directives.encoding,
'tab-width': int,
'start-line': int,
'end-line': int}
def run(self):
"""Include a file as part of the content of this reST file."""
if not self.state.document.settings.file_insertion_enabled:
raise self.warning('"%s" directive disabled.' % self.name)
source = self.state_machine.input_lines.source(
self.lineno - self.state_machine.input_offset - 1)
source_dir = os.path.dirname(os.path.abspath(source))
path = directives.path(self.arguments[0])
path = os.path.normpath(os.path.join(source_dir, path))
path = utils.relative_path(None, path)
path = nodes.reprunicode(path)
encoding = self.options.get(
'encoding', self.state.document.settings.input_encoding)
e_handler = self.state.document.settings.input_encoding_error_handler
tab_width = self.options.get(
'tab-width', self.state.document.settings.tab_width)
try:
self.state.document.settings.record_dependencies.add(path)
include_file = io.FileInput(source_path=path,
encoding=encoding,
error_handler=e_handler)
except UnicodeEncodeError as error:
raise self.severe('Problems with "%s" directive path:\n'
'Cannot encode input file path "%s" '
'(wrong locale?).' %
(self.name, SafeString(path)))
except IOError as error:
raise self.severe('Problems with "%s" directive path:\n%s.' %
(self.name, ErrorString(error)))
startline = self.options.get('start-line', None)
endline = self.options.get('end-line', None)
try:
if startline or (endline is not None):
lines = include_file.readlines()
rawtext = ''.join(lines[startline:endline])
else:
rawtext = include_file.read()
except UnicodeError as error:
raise self.severe('Problem with "%s" directive:\n%s' %
(self.name, ErrorString(error)))
include_lines = statemachine.string2lines(rawtext, tab_width,
convert_whitespace=True)
# default lexer to 'text'
lexer = self.options.get('lexer', 'text')
self.options['source'] = path
codeblock = Pygments(self.name,
[lexer], # arguments
{}, # no options for this directive
include_lines, # content
self.lineno,
self.content_offset,
self.block_text,
self.state,
self.state_machine)
return codeblock.run()
def register():
directives.register_directive('code-include', CodeInclude)
+60
View File
@@ -0,0 +1,60 @@
Disqus static comment plugin for Pelican
====================================
This plugin adds a disqus_comments property to all articles.
Comments are fetched at generation time using disqus API.
Installation
------------
Because we use disqus API to retrieve the comments you need to create an application at
http://disqus.com/api/applications/ which will provide you with a secret and public keys for the API.
We use disqus-python package for communication with disqus API:
``pip install disqus-python``
Put ``disqus_static.py`` plugin in ``plugins`` folder in pelican installation
and use the following in your settings::
PLUGINS = [u"pelican.plugins.disqus_static"]
DISQUS_SITENAME = u'YOUR_SITENAME'
DISQUS_SECRET_KEY = u'YOUR_SECRET_KEY'
DISQUS_PUBLIC_KEY = u'YOUR_PUBLIC_KEY'
Usage
-----
.. code-block:: html+jinja
{% if article.disqus_comments %}
<div id="disqus_static_comments">
<h4>{{ article.disqus_comment_count }} comments</h4>
<ul class="post-list">
{% for comment in article.disqus_comments recursive %}
<li class="post">
<div class="post-content">
<div class="avatar hovercard">
<img alt="Avatar" src="{{ comment.author.avatar.small.cache }}">
</div>
<div class="post-body">
<header>
<span class="publisher-anchor-color">{{ comment.author.name }}</span>
<span class="time-ago" title="{{ comment.createdAt }}">{{ comment.createdAt }}</span>
</header>
<div class="post-message-container">
<div class="post-message publisher-anchor-color ">
{{ comment.message }}
</div>
</div>
</div>
</div>
{% if comment.children %}
<ul class="children">
{{ loop(comment.children) }}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
{% endif %}
+79
View File
@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
"""
Disqus static comment plugin for Pelican
====================================
This plugin adds a disqus_comments property to all articles.
Comments are fetched at generation time using disqus API.
"""
from disqusapi import DisqusAPI, Paginator
from pelican import signals
def initialized(pelican):
from pelican.settings import _DEFAULT_CONFIG
_DEFAULT_CONFIG.setdefault('DISQUS_SECRET_KEY', '')
_DEFAULT_CONFIG.setdefault('DISQUS_PUBLIC_KEY', '')
if pelican:
pelican.settings.setdefault('DISQUS_SECRET_KEY', '')
pelican.settings.setdefault('DISQUS_PUBLIC_KEY', '')
def disqus_static(generator):
disqus = DisqusAPI(generator.settings['DISQUS_SECRET_KEY'],
generator.settings['DISQUS_PUBLIC_KEY'])
# first retrieve the threads
threads = Paginator(disqus.threads.list,
forum=generator.settings['DISQUS_SITENAME'])
# build a {thread_id: title} dict
thread_dict = {}
for thread in threads:
thread_dict[thread['id']] = thread['title']
# now retrieve the posts
posts = Paginator(disqus.posts.list,
forum=generator.settings['DISQUS_SITENAME'])
# build a {post_id: [child_post1, child_post2, ...]} dict
child_dict = {}
for post in posts:
if post['id'] not in child_dict.keys():
child_dict[post['id']] = []
if post['parent'] is not None:
if str(post['parent']) not in child_dict.keys():
child_dict[str(post['parent'])] = []
child_dict[str(post['parent'])].append(post)
# build a {title: [post1, post2, ...]} dict
post_dict = {}
for post in posts:
build_post_dict(post_dict, child_dict, thread_dict, post)
for article in generator.articles:
if article.title in post_dict:
article.disqus_comments = post_dict[article.title]
article.disqus_comment_count = sum([
postcounter(post) for post in post_dict[article.title]])
def postcounter(node):
return 1 + sum([postcounter(n) for n in node['children']])
def build_post_dict(post_dict, child_dict, thread_dict, post):
if post['thread'] not in thread_dict.keys():
return # invalid thread, should never happen
build_child_dict(child_dict, post)
if post['parent'] is not None:
return # this is a child post, don't want to display it here
if thread_dict[post['thread']] not in post_dict.keys():
post_dict[thread_dict[post['thread']]] = []
post_dict[thread_dict[post['thread']]].append(post)
def build_child_dict(child_dict, post):
post['children'] = child_dict[post['id']]
for child in child_dict[post['id']]:
build_child_dict(child_dict, child)
def register():
signals.initialized.connect(initialized)
signals.article_generator_finalized.connect(disqus_static)
+44
View File
@@ -54,6 +54,50 @@ ToC generated by Markdown is enclosed in `<div class="toc">`. On the other hand
ToC generated by reST is enclosed in `<div class="contents topic">`.
`extract_toc` relies on this behavior to work.
reStructuredText Example
------------------------
To add a table of contents to your reStructuredText document you need to add a 'contents directive' at the place where you want the table of contents to appear. See the [documentation](http://docutils.sourceforge.net/docs/ref/rst/directives.html#table-of-contents) for more details.
```rst
My super title
##############
:date: 2010-10-03
:tags: thats, awesome
.. contents::
..
1 Head 1
1.1 Head 2
2 Head 3
3 head 4
Heading 1
---------
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.
```
Markdown Example
----------------
To add a table of contents to your Markdown document you need to place the 'TOC marker' at the place where you would like the table of contents to appear. See the Python Markdown [documentation](http://pythonhosted.org/Markdown/extensions/toc.html) for more details.
Important! To enable table of contents generation for the markdown reader you need to set `MD_EXTENSIONS = (['toc'])` in your pelican configuration file.
```Markdown
title: My super title
date: 4-4-2013
tags: thats, awesome
[TOC]
# Heading 1 #
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.
```
Template Example
================
+4 -2
View File
@@ -8,11 +8,13 @@ and place it in its own article.toc variable.
from os import path
from bs4 import BeautifulSoup
from pelican import signals, readers
from pelican import signals, readers, contents
def extract_toc(content):
soup = BeautifulSoup(content._content)
if isinstance(content, contents.Static):
return
soup = BeautifulSoup(content._content,'html.parser')
filename = content.source_path
extension = path.splitext(filename)[1][1:]
toc = ''
+39
View File
@@ -0,0 +1,39 @@
ical
----
This plugin looks for and parses an ``.ics`` file if it is defined in a given
page's ``calendar`` metadata. One calendar can be defined per page.
Dependencies
------------
This plugin depends on the ``icalendar`` package, which can be installed via
pip::
pip install icalendar
Usage
-----
For a reST-formatted page, include the following line in the metadata::
:calendar: /path/to/your/ics/file
For Markdown, include the following line in the page metadata::
Calendar: /path/to/your/ics/file
Following is some example code that can be added to your theme's ``page.html``
template in order to display the calendar::
{% if page.calendar %}
<dl>
{% for vevent in events[page.slug] %}
<dt>{{ vevent.summary }}</dt>
<dd>{{ vevent.description|replace('\n\n', '<br>') }}</dd>
<dd>{{ vevent.dtstart }}</dd>
<dd>{{ vevent.dtend }}</dd>
<dd class="footer"><a href="{{ vevent.url }}">See more</a></dd>
{% endfor %}
</dl>
{% endif %}
+1
View File
@@ -0,0 +1 @@
from .ical import *
+52
View File
@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
"""
ical plugin for Pelican
=======================
This plugin looks for and parses an .ics file if it is defined in a given
page's :calendar: metadata. One calendar can be defined per page.
"""
from icalendar import Calendar, Event
from pelican import signals , utils
import pytz
import datetime
import os.path
def init_cal(generator):
# initialisation of the calendar dictionary
# you can add one calendar per page
calDict = {}
generator.context['events'] = calDict
def add_ical(generator, metadata):
# check if a calendar is here
if 'calendar' in metadata.keys():
summ = []
path = metadata['calendar']
if not os.path.isabs(path):
path = os.path.abspath(metadata['calendar'])
cal = Calendar.from_ical(open(path,'rb').read())
for element in cal.walk():
eventdict = {}
if element.name == "VEVENT":
if element.get('summary') != None:
eventdict['summary'] = element.get('summary')
if element.get('description') != None:
eventdict['description'] = element.get('description')
if element.get('url') != None:
eventdict['url'] = element.get('url')
if element.get('dtstart') != None:
eventdict['dtstart'] = element.get('dtstart').dt
if element.get('dtend') != None:
eventdict['dtend'] = element.get('dtend').dt
summ.append(eventdict)
# the id of the calendar is the slugified name of the page
calId = utils.slugify(metadata['title'])
generator.context['events'][calId] = summ
def register():
signals.pages_generator_init.connect(init_cal)
signals.pages_generate_context.connect(add_ical)
+3
View File
@@ -30,6 +30,9 @@ between the `<head>` parameters (for the NotMyIdea template, this file is base.h
{% if article and article.latex %}
{{ article.latex }}
{% endif %}
{% if page and page.latex %}
{{ page.latex }}
{% endif %}
Usage
-----
+1
View File
@@ -45,3 +45,4 @@ def register():
Plugin registration
"""
signals.article_generate_context.connect(addLatex)
signals.pages_generate_context.connect(addLatex)
+26
View File
@@ -0,0 +1,26 @@
Optimize Images Plugin For Pelican
==================================
This plugin applies lossless compression on JPEG and PNG images, with no
effect on image quality. It uses [jpegtran][1] and [OptiPNG][2]. It assumes
that both of these tools are installed on system path.
[1]: http://jpegclub.org/jpegtran/ "jpegtran"
[2]: http://optipng.sourceforge.net/ "OptiPNG"
Installation
------------
To enable, ensure that `optimize_images.py` is put somewhere that is accessible.
Then use as follows by adding the following to your settings.py:
PLUGIN_PATH = 'path/to/pelican-plugins'
PLUGINS = ["optimize_images"]
`PLUGIN_PATH` can be a path relative to your settings file or an absolute path.
Usage
-----
The plugin will activate and optimize images upon `finalized` signal of
pelican.
+1
View File
@@ -0,0 +1 @@
from .optimize_images import *
+51
View File
@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
"""
Optimized images (jpg and png)
Assumes that jpegtran and optipng are isntalled on path.
http://jpegclub.org/jpegtran/
http://optipng.sourceforge.net/
Copyright (c) 2012 Irfan Ahmad (http://i.com.pk)
"""
import logging
import os
from subprocess import call
from pelican import signals
logger = logging.getLogger(__name__)
# A list of file types with their respective commands
COMMANDS = [
('.jpg', 'jpegtran -copy none -optimize "{filename}" "{filename}"'),
('.png', 'optipng "{filename}"'),
]
def optimize_images(pelican):
"""
Optimized jpg and png images
:param pelican: The Pelican instance
"""
for dirpath, _, filenames in os.walk(pelican.settings['OUTPUT_PATH']):
for name in filenames:
optimize(dirpath, name)
def optimize(dirpath, filename):
"""
Check if the name is a type of file that should be optimized.
And optimizes it if required.
:param dirpath: Path of the file to be optimzed
:param name: A file name to be optimized
"""
for extension, command in COMMANDS:
if filename.endswith(extension):
filepath = os.path.join(dirpath, filename)
command = command.format(filename=filepath)
call(command, shell=True)
def register():
signals.finalized.connect(optimize_images)
+5 -5
View File
@@ -11,11 +11,11 @@ import types
from pelican import signals
def initialized(pelican):
from pelican.settings import _DEFAULT_CONFIG
_DEFAULT_CONFIG.setdefault('SUMMARY_BEGIN_MARKER',
'<!-- PELICAN_BEGIN_SUMMARY -->')
_DEFAULT_CONFIG.setdefault('SUMMARY_END_MARKER',
'<!-- PELICAN_END_SUMMARY -->')
from pelican.settings import DEFAULT_CONFIG
DEFAULT_CONFIG.setdefault('SUMMARY_BEGIN_MARKER',
'<!-- PELICAN_BEGIN_SUMMARY -->')
DEFAULT_CONFIG.setdefault('SUMMARY_END_MARKER',
'<!-- PELICAN_END_SUMMARY -->')
if pelican:
pelican.settings.setdefault('SUMMARY_BEGIN_MARKER',
'<!-- PELICAN_BEGIN_SUMMARY -->')