From 073c997eea7a77c47cf55a8cbb04bb81aaf1c370 Mon Sep 17 00:00:00 2001 From: Talha Mansoor Date: Fri, 7 Feb 2014 11:40:48 +0500 Subject: [PATCH 01/26] Fix an issue in neighbors plugin which was introduced after subcategory support It is not necessary that articles will have subcategories. You may end up getting following critical error. > CRITICAL: ("'ArticlesGenerator' object has no attribute 'subcategories'",) CRITICAL: 'ArticlesGenerator' object has no attribute 'subcategories' Traceback (most recent call last): File "/Users/talha/Repos/VirtualEnvs/pelican-dev/bin/pelican", line 8, in load_entry_point('pelican==3.3', 'console_scripts', 'pelican')() File "/Users/talha/Repos/VirtualEnvs/pelican-dev/lib/python2.7/site-packages/pelican-3.3-py2.7.egg/pelican/__init__.py", line 346, in main pelican.run() File "/Users/talha/Repos/VirtualEnvs/pelican-dev/lib/python2.7/site-packages/pelican-3.3-py2.7.egg/pelican/__init__.py", line 162, in run p.generate_context() File "/Users/talha/Repos/VirtualEnvs/pelican-dev/lib/python2.7/site-packages/pelican-3.3-py2.7.egg/pelican/generators.py", line 480, in generate_context signals.article_generator_finalized.send(self) File "/Users/talha/Repos/VirtualEnvs/pelican-dev/lib/python2.7/site-packages/blinker/base.py", line 267, in send for receiver in self.receivers_for(sender)] File "/Users/talha/Repos/pelican-plugins/neighbors/neighbors.py", line 49, in neighbors for subcategory, articles in generator.subcategories: AttributeError: 'ArticlesGenerator' object has no attribute 'subcategories' --- neighbors/neighbors.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/neighbors/neighbors.py b/neighbors/neighbors.py index a6dd2f4..b65fcfe 100755 --- a/neighbors/neighbors.py +++ b/neighbors/neighbors.py @@ -45,13 +45,14 @@ def neighbors(generator): articles.sort(key=(lambda x: x.date), reverse=(True)) set_neighbors( articles, 'next_article_in_category', 'prev_article_in_category') - - for subcategory, articles in generator.subcategories: - articles.sort(key=(lambda x: x.date), reverse=(True)) - index = subcategory.name.count('/') - next_name = 'next_article_in_subcategory{}'.format(index) - prev_name = 'prev_article_in_subcategory{}'.format(index) - set_neighbors(articles, next_name, prev_name) + + if hasattr(generator, 'subcategories'): + for subcategory, articles in generator.subcategories: + articles.sort(key=(lambda x: x.date), reverse=(True)) + index = subcategory.name.count('/') + next_name = 'next_article_in_subcategory{}'.format(index) + prev_name = 'prev_article_in_subcategory{}'.format(index) + set_neighbors(articles, next_name, prev_name) def register(): signals.article_generator_finalized.connect(neighbors) From 3bf1ac5e9832519f01cead61abe6588bd2c88056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Berg=20Baugsson=20Sigr=C3=AD=C3=B0arson?= Date: Sun, 9 Feb 2014 00:54:01 +0000 Subject: [PATCH 02/26] Fixed missing quote mark in an HTML example --- subcategory/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subcategory/README.md b/subcategory/README.md index 21c610f..015bea0 100644 --- a/subcategory/README.md +++ b/subcategory/README.md @@ -28,7 +28,7 @@ breadcrumb-style navigation you might try something like this: {% for subcategory in article.subcategories %}
  • - {{ subcategory.shortname }}
  • {% endfor %} From b35996ca430c6d069fcfff751a8019d7bbd3737f Mon Sep 17 00:00:00 2001 From: Mario Lang Date: Mon, 10 Feb 2014 17:27:54 +0100 Subject: [PATCH 03/26] Allow for an optional maximum number of entries to display. --- github_activity/Readme.rst | 5 +++++ github_activity/github_activity.py | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/github_activity/Readme.rst b/github_activity/Readme.rst index fa3b95d..5e17eb0 100644 --- a/github_activity/Readme.rst +++ b/github_activity/Readme.rst @@ -9,6 +9,11 @@ For example, to track Pelican project activity, the setting would be:: GITHUB_ACTIVITY_FEED = 'https://github.com/getpelican.atom' +If you want to limit the amount of entries to a certain maximum set the +``GITHUB_ACTIVITY_MAX_ENTRIES`` parameter. + + GITHUB_ACTIVITY_MAX_ENTRIES = 10 + On the template side, you just have to iterate over the ``github_activity`` variable, as in this example:: diff --git a/github_activity/github_activity.py b/github_activity/github_activity.py index fb3b2b0..76e3405 100644 --- a/github_activity/github_activity.py +++ b/github_activity/github_activity.py @@ -25,6 +25,7 @@ class GitHubActivity(): import feedparser self.activities = feedparser.parse( generator.settings['GITHUB_ACTIVITY_FEED']) + self.max_entries = generator.settings['GITHUB_ACTIVITY_MAX_ENTRIES'] def fetch(self): """ @@ -37,7 +38,7 @@ class GitHubActivity(): [element for element in [activity['title'], activity['content'][0]['value']]]) - return entries + return entries[0:self.max_entries] def fetch_github_activity(gen, metadata): From c85ea081c076f63fad432267bb00d9ec038ab587 Mon Sep 17 00:00:00 2001 From: sil Date: Sat, 15 Feb 2014 01:21:06 +0000 Subject: [PATCH 04/26] Simple Footnotes plugin. Add footnotes to your text by enclosing them in [ref] and [/ref]. --- simple_footnotes/README.md | 26 +++++++ simple_footnotes/__init__.py | 1 + simple_footnotes/simple_footnotes.py | 91 +++++++++++++++++++++++ simple_footnotes/test_simple_footnotes.py | 33 ++++++++ 4 files changed, 151 insertions(+) create mode 100644 simple_footnotes/README.md create mode 100644 simple_footnotes/__init__.py create mode 100644 simple_footnotes/simple_footnotes.py create mode 100644 simple_footnotes/test_simple_footnotes.py diff --git a/simple_footnotes/README.md b/simple_footnotes/README.md new file mode 100644 index 0000000..d4105cc --- /dev/null +++ b/simple_footnotes/README.md @@ -0,0 +1,26 @@ +Simple Footnotes +================ + +A Pelican plugin to add footnotes to blog posts. + +When writing a post or page, add a footnote like this: + + Here's my written text[ref]and here is a footnote[/ref]. + +This will appear as, roughly: + +Here's my written text1 + + 1. and here is a footnote ↩ + +Inspired by Andrew Nacin's [Simple Footnotes WordPress plugin](http://wordpress.org/plugins/simple-footnotes/). + +Requirements +============ + +Needs html5lib, so you'll want to `pip install html5lib` before running. + +Should work with any content format (ReST, Markdown, whatever), because +it looks for the `[ref]` and `[/ref]` once the conversion to HTML has happened. + +Stuart Langridge, http://www.kryogenix.org/, February 2014. diff --git a/simple_footnotes/__init__.py b/simple_footnotes/__init__.py new file mode 100644 index 0000000..2f958a8 --- /dev/null +++ b/simple_footnotes/__init__.py @@ -0,0 +1 @@ +from .simple_footnotes import * diff --git a/simple_footnotes/simple_footnotes.py b/simple_footnotes/simple_footnotes.py new file mode 100644 index 0000000..ea2e480 --- /dev/null +++ b/simple_footnotes/simple_footnotes.py @@ -0,0 +1,91 @@ +from pelican import signals +import re +import html5lib + +RAW_FOOTNOTE_CONTAINERS = ["code"] + +def getText(node, recursive = False): + """Get all the text associated with this node. + With recursive == True, all text from child nodes is retrieved.""" + L = [''] + for n in node.childNodes: + if n.nodeType in (node.TEXT_NODE, node.CDATA_SECTION_NODE): + L.append(n.data) + else: + if not recursive: + return None + L.append(getText(n) ) + return ''.join(L) + +def parse_for_footnotes(article_generator): + for article in article_generator.articles: + if "[ref]" in article._content and "[/ref]" in article._content: + content = article._content.replace("[ref]", "").replace("[/ref]", "") + parser = html5lib.HTMLParser(tree=html5lib.getTreeBuilder("dom")) + dom = parser.parse(content) + endnotes = [] + count = 0 + for footnote in dom.getElementsByTagName("x-simple-footnote"): + pn = footnote + leavealone = False + while pn: + if pn.nodeName in RAW_FOOTNOTE_CONTAINERS: + leavealone = True + break + pn = pn.parentNode + if leavealone: + continue + count += 1 + fnid = "sf-%s-%s" % (article.slug, count) + fnbackid = "%s-back" % (fnid,) + endnotes.append((footnote, fnid, fnbackid)) + number = dom.createElement("sup") + number.setAttribute("id", fnbackid) + numbera = dom.createElement("a") + numbera.setAttribute("href", "#%s" % fnid) + numbera.setAttribute("class", "simple-footnote") + numbera.appendChild(dom.createTextNode(str(count))) + txt = getText(footnote, recursive=True).replace("\n", " ") + numbera.setAttribute("title", txt) + number.appendChild(numbera) + footnote.parentNode.insertBefore(number, footnote) + if endnotes: + ol = dom.createElement("ol") + ol.setAttribute("class", "simple-footnotes") + for e, fnid, fnbackid in endnotes: + li = dom.createElement("li") + li.setAttribute("id", fnid) + while e.firstChild: + li.appendChild(e.firstChild) + backlink = dom.createElement("a") + backlink.setAttribute("href", "#%s" % fnbackid) + backlink.setAttribute("class", "simple-footnote-back") + backlink.appendChild(dom.createTextNode(u'\u21a9')) + li.appendChild(dom.createTextNode(" ")) + li.appendChild(backlink) + ol.appendChild(li) + e.parentNode.removeChild(e) + dom.getElementsByTagName("body")[0].appendChild(ol) + s = html5lib.serializer.htmlserializer.HTMLSerializer(omit_optional_tags=False, quote_attr_values=True) + output_generator = s.serialize(html5lib.treewalkers.getTreeWalker("dom")(dom.getElementsByTagName("body")[0])) + article._content = "".join(list(output_generator)).replace( + "", "[ref]").replace("", "[/ref]").replace( + "", "").replace("", "") + if False: + count = 0 + endnotes = [] + for f in footnotes: + count += 1 + fnstr = '%s' % ( + article.slug, count, article.slug, count, count) + endstr = '
  • %s
  • ' % ( + article.slug, count, f[len("[ref]"):-len("[/ref]")], article.slug, count) + content = content.replace(f, fnstr) + endnotes.append(endstr) + content += '

    Footnotes

      %s' % ("\n".join(endnotes),) + article._content = content + + +def register(): + signals.article_generator_finalized.connect(parse_for_footnotes) + diff --git a/simple_footnotes/test_simple_footnotes.py b/simple_footnotes/test_simple_footnotes.py new file mode 100644 index 0000000..04af1dc --- /dev/null +++ b/simple_footnotes/test_simple_footnotes.py @@ -0,0 +1,33 @@ +import unittest +from simple_footnotes import parse_for_footnotes + +class PseudoArticleGenerator(object): + articles = [] +class PseudoArticle(object): + _content = "" + slug = "article" + +class TestFootnotes(unittest.TestCase): + + def _expect(self, input, expected_output): + ag = PseudoArticleGenerator() + art = PseudoArticle() + art._content = input + ag.articles = [art] + parse_for_footnotes(ag) + self.assertEqual(art._content, expected_output) + + def test_simple(self): + self._expect("words[ref]footnote[/ref]end", + ('words1end' + '
        ' + u'
      1. footnote \u21a9
      2. ' + '
      ')) + + def test_no_footnote_inside_code(self): + self._expect("wordsthis is code[ref]footnote[/ref] end code end", + "wordsthis is code[ref]footnote[/ref] end code end") + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From b86e17607c13e5ae1a607f589fcf7ae1d4000709 Mon Sep 17 00:00:00 2001 From: Samy Arous Date: Sun, 16 Feb 2014 19:23:48 +0100 Subject: [PATCH 05/26] Initial release of the twitter bootstrap rst directives plugin --- twitter_bootstrap_rst_directives/Demo.rst | 118 ++++ twitter_bootstrap_rst_directives/Readme.rst | 46 ++ twitter_bootstrap_rst_directives/__init__.py | 4 + .../bootstrap_rst_directives.py | 543 ++++++++++++++++++ 4 files changed, 711 insertions(+) create mode 100644 twitter_bootstrap_rst_directives/Demo.rst create mode 100644 twitter_bootstrap_rst_directives/Readme.rst create mode 100644 twitter_bootstrap_rst_directives/__init__.py create mode 100644 twitter_bootstrap_rst_directives/bootstrap_rst_directives.py diff --git a/twitter_bootstrap_rst_directives/Demo.rst b/twitter_bootstrap_rst_directives/Demo.rst new file mode 100644 index 0000000..c38ae10 --- /dev/null +++ b/twitter_bootstrap_rst_directives/Demo.rst @@ -0,0 +1,118 @@ +This will be turned into :abbr:`HTML (HyperText Markup Language)`. + +Love this music :glyph:`music` + +.. role:: story_time_glyph(glyph) + :target: http://www.youtube.com/watch?v=5g8ykQLYnX0 + :class: small text-info + +Love this music :story_time_glyph:`music` + +This is an example of code: :code:``. + +This is an example of kbd: :kbd:``. + + +.. label-default:: + + This is a default label content + +.. label-primary:: + + This is a primary label content + +.. label-success:: + + This is a success label content + +.. label-info:: + + This is a info label content + +.. label-warning:: + + This is a warning label content + +.. label-danger:: + + This is a danger label content + + +.. panel-default:: + :title: panel default title + + This is a default panel content + +.. panel-primary:: + :title: panel primary title + + This is a primary panel content + +.. panel-success:: + :title: panel success title + + This is a success panel content + +.. panel-info:: + :title: panel info title + + This is a info panel content + +.. panel-warning:: + :title: panel warning title + + This is a warning panel content + +.. panel-danger:: + :title: panel danger title + + This is a danger panel content + + +.. alert-success:: + + This is a success alert content + +.. alert-info:: + + This is a info alert content + +.. alert-warning:: + + This is a warning alert content + +.. alert-danger:: + + This is a danger alert content + + +.. media:: http://stuffkit.com/wp-content/uploads/2012/11/Worlds-Most-Beautiful-Lady-Camilla-Belle-HD-Photos-4.jpg + :height: 750 + :width: 1000 + :scale: 20 + :target: http://www.google.com + :alt: Camilla Belle + :position: left + + .. class:: h3 + + left position + + This image is not mine. Credit goes to http://stuffkit.com + + + +.. media:: http://stuffkit.com/wp-content/uploads/2012/11/Worlds-Most-Beautiful-Lady-Camilla-Belle-HD-Photos-4.jpg + :height: 750 + :width: 1000 + :scale: 20 + :target: http://www.google.com + :alt: Camilla Belle + :position: right + + .. class:: h3 + + right position + + + This image is not mine. Credit goes to http://stuffkit.com \ No newline at end of file diff --git a/twitter_bootstrap_rst_directives/Readme.rst b/twitter_bootstrap_rst_directives/Readme.rst new file mode 100644 index 0000000..f84417c --- /dev/null +++ b/twitter_bootstrap_rst_directives/Readme.rst @@ -0,0 +1,46 @@ +Twitter Bootstrap Directive for restructured text +------------------------------------------------- + +This plugin defines some rst directive that enable a clean usage of the twitter bootstrap CSS and Javascript components. + +Directives +---------- + +Implemented directives: + + label, + alert, + panel, + media + +Implemented roles: + + glyph, + code, + kbd + +Usage +----- + +For more informations about the usage of each directive, read the corresponding class description. +Or checkout this demo page. + +Dependencies +------------ + +In order to use this plugin, you need to use a template that supports bootstrap 3.1.1 with the glyph font setup +correctly. Usually you should have this structure:: + + static + ├── css + | └── bootstrap.min.css + ├── font + | └── glyphicons-halflings-regular.ttf + └── js + └── + +Warning +------- + +In order to support some unique features and avoid conflicts with bootstrap, this plugin will use a custom html writer which +is modifying the traditional docutils output. \ No newline at end of file diff --git a/twitter_bootstrap_rst_directives/__init__.py b/twitter_bootstrap_rst_directives/__init__.py new file mode 100644 index 0000000..7dfdc27 --- /dev/null +++ b/twitter_bootstrap_rst_directives/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from .bootstrap_rst_directives import * diff --git a/twitter_bootstrap_rst_directives/bootstrap_rst_directives.py b/twitter_bootstrap_rst_directives/bootstrap_rst_directives.py new file mode 100644 index 0000000..8385134 --- /dev/null +++ b/twitter_bootstrap_rst_directives/bootstrap_rst_directives.py @@ -0,0 +1,543 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + + +""" +Twitter Bootstrap RST directives Plugin For Pelican +=================================================== + +This plugin defines rst directives for different CSS and Javascript components from +the twitter bootstrap framework. + +""" + +from uuid import uuid1 + +from cgi import escape +from docutils import nodes, utils +import docutils +from docutils.parsers import rst +from docutils.parsers.rst import directives, roles, Directive +from pelican import signals +from pelican.readers import RstReader, PelicanHTMLTranslator + + + +class CleanHTMLTranslator(PelicanHTMLTranslator): + + """ + A custom HTML translator based on the Pelican HTML translator. + Used to clean up some components html classes that could conflict + with the bootstrap CSS classes. + Also defines new tags that are not handleed by the current implementation of + docutils. + + The most obvious example is the Container component + """ + + def visit_literal(self, node): + classes = node.get('classes', node.get('class', [])) + if 'code' in classes: + self.body.append(self.starttag(node, 'code')) + elif 'kbd' in classes: + self.body.append(self.starttag(node, 'kbd')) + else: + self.body.append(self.starttag(node, 'pre')) + + def depart_literal(self, node): + classes = node.get('classes', node.get('class', [])) + if 'code' in classes: + self.body.append('\n') + elif 'kbd' in classes: + self.body.append('\n') + else: + self.body.append('\n') + + def visit_container(self, node): + self.body.append(self.starttag(node, 'div')) + + +class CleanRSTReader(RstReader): + + """ + A custom RST reader that behaves exactly like its parent class RstReader with + the difference that it uses the CleanHTMLTranslator + """ + + def _get_publisher(self, source_path): + extra_params = {'initial_header_level': '2', + 'syntax_highlight': 'short', + 'input_encoding': 'utf-8'} + user_params = self.settings.get('DOCUTILS_SETTINGS') + if user_params: + extra_params.update(user_params) + + pub = docutils.core.Publisher( + destination_class=docutils.io.StringOutput) + pub.set_components('standalone', 'restructuredtext', 'html') + pub.writer.translator_class = CleanHTMLTranslator + pub.process_programmatic_settings(None, extra_params, None) + pub.set_source(source_path=source_path) + pub.publish() + return pub + + +def keyboard_role(name, rawtext, text, lineno, inliner, + options={}, content=[]): + """ + This function creates an inline console input block as defined in the twitter bootstrap documentation + overrides the default behaviour of the kbd role + + *usage:* + :kbd:`` + + *Example:* + + :kbd:`
      ` + + This code is not highlighted + """ + new_element = nodes.literal(rawtext, text) + new_element.set_class('kbd') + + return [new_element], [] + + +def code_role(name, rawtext, text, lineno, inliner, + options={}, content=[]): + """ + This function creates an inline code block as defined in the twitter bootstrap documentation + overrides the default behaviour of the code role + + *usage:* + :code:`` + + *Example:* + + :code:`
      ` + + This code is not highlighted + """ + new_element = nodes.literal(rawtext, text) + new_element.set_class('code') + + return [new_element], [] + + +def glyph_role(name, rawtext, text, lineno, inliner, + options={}, content=[]): + """ + This function defines a glyph inline role that show a glyph icon from the + twitter bootstrap framework + + *Usage:* + + :glyph:`` + + *Example:* + + Love this music :glyph:`music` :) + + Can be subclassed to include a target + + *Example:* + + .. role:: story_time_glyph(glyph) + :target: http://www.youtube.com/watch?v=5g8ykQLYnX0 + :class: small text-info + + Love this music :story_time_glyph:`music` :) + + """ + + target = options.get('target', None) + glyph_name = 'glyphicon-{}'.format(text) + + if target: + target = utils.unescape(target) + new_element = nodes.reference(rawtext, ' ', refuri=target) + else: + new_element = nodes.container() + classes = options.setdefault('class', []) + classes += ['glyphicon', glyph_name] + for custom_class in classes: + new_element.set_class(custom_class) + return [new_element], [] + +glyph_role.options = { + 'target': rst.directives.unchanged, +} +glyph_role.content = False + + +class Label(rst.Directive): + + ''' + generic Label directive class definition. + This class define a directive that shows + bootstrap Labels around its content + + *usage:* + + .. label-:: + +