Sfoglia il codice sorgente

Merge branch 'master' into better_figures_and_images

Duncan Lock 11 anni fa
parent
commit
1ce4ae803b

+ 8 - 7
Contributing.rst

@@ -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 - 0
LICENSE

@@ -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 - 0
code_include/README.rst

@@ -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 - 0
code_include/__init__.py

@@ -0,0 +1 @@
+from code_include import *

+ 87 - 0
code_include/code_include.py

@@ -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 - 0
disqus_static/README.rst

@@ -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 - 0
disqus_static/disqus_static.py

@@ -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 - 0
extract_toc/README.md

@@ -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
extract_toc/extract_toc.py

@@ -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 - 0
ical/Readme.rst

@@ -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 - 0
ical/__init__.py

@@ -0,0 +1 @@
+from .ical import *

+ 52 - 0
ical/ical.py

@@ -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 - 0
latex/Readme.md

@@ -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 - 0
latex/latex.py

@@ -45,3 +45,4 @@ def register():
         Plugin registration
     """
     signals.article_generate_context.connect(addLatex)
+    signals.pages_generate_context.connect(addLatex)

+ 26 - 0
optimize_images/Readme.md

@@ -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 - 0
optimize_images/__init__.py

@@ -0,0 +1 @@
+from .optimize_images import *

+ 51 - 0
optimize_images/optimize_images.py

@@ -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
summary/summary.py

@@ -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 -->')