enable plugins
This commit is contained in:
parent
93dc9efd86
commit
11986f6e9b
@ -35,8 +35,8 @@ EXTRA_PATH_METADATA = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#Pelican Plugins & Markdown extensions
|
#Pelican Plugins & Markdown extensions
|
||||||
#PLUGIN_PATHS = ['plugins']
|
PLUGIN_PATHS = ['plugins']
|
||||||
#PLUGINS = ['extract_toc', 'summary','representative_image','addressable_paragraphs']
|
PLUGINS = ['extract_toc', 'summary','representative_image','addressable_paragraphs']
|
||||||
MARKDOWN = {'extensions':
|
MARKDOWN = {'extensions':
|
||||||
['markdown.extensions.codehilite',
|
['markdown.extensions.codehilite',
|
||||||
'markdown.extensions.extra',
|
'markdown.extensions.extra',
|
||||||
|
1
plugins/addressable_paragraphs/__init__.py
Normal file
1
plugins/addressable_paragraphs/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .addressable_paragraphs import *
|
48
plugins/addressable_paragraphs/addressable_paragraphs.py
Normal file
48
plugins/addressable_paragraphs/addressable_paragraphs.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
"""
|
||||||
|
Addressable Paragraphs
|
||||||
|
------------------------
|
||||||
|
In converting from .md to .html, images are wrapped in <p> tags.
|
||||||
|
This plugin gives those paragraphs the 'img' class for styling enhancements.
|
||||||
|
In case there is any description immediately following that image, it is wrapped in another paragraph with the 'caption' class.
|
||||||
|
|
||||||
|
Copyright (C) 2018 Roel Roscam Abbing
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from pelican import signals
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
def content_object_init(instance):
|
||||||
|
|
||||||
|
if instance._content is not None:
|
||||||
|
content = instance._content
|
||||||
|
soup = BeautifulSoup(content, 'html.parser')
|
||||||
|
|
||||||
|
for p in soup(['p', 'object']):
|
||||||
|
if p.findChild('img'):
|
||||||
|
p.attrs['class'] = 'img'
|
||||||
|
caption = soup.new_tag('p',**{'class':'caption'})
|
||||||
|
if len(p.contents) > 1: #if we have more than just the <img> tag
|
||||||
|
for i in reversed(p.contents):
|
||||||
|
if i.name != 'img': #if it is not an <img> tag
|
||||||
|
caption.insert(0,i.extract())
|
||||||
|
p.insert_after(caption)
|
||||||
|
|
||||||
|
instance._content = soup.decode()
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
signals.content_object_init.connect(content_object_init)
|
137
plugins/extract_toc/README.md
Normal file
137
plugins/extract_toc/README.md
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
Extract Table of Content
|
||||||
|
========================
|
||||||
|
|
||||||
|
A Pelican plugin to extract table of contents (ToC) from `article.content` and
|
||||||
|
place it in its own `article.toc` variable for use in templates.
|
||||||
|
|
||||||
|
Copyright (c) Talha Mansoor
|
||||||
|
|
||||||
|
Author | Talha Mansoor
|
||||||
|
----------------|-----
|
||||||
|
Author Email | talha131@gmail.com
|
||||||
|
Author Homepage | http://onCrashReboot.com
|
||||||
|
Github Account | https://github.com/talha131
|
||||||
|
|
||||||
|
|
||||||
|
Acknowledgement
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Thanks to [Avaris](https://github.com/avaris) for going out of the way to help
|
||||||
|
me fix Unicode issues and doing a thorough code review.
|
||||||
|
|
||||||
|
Thanks to [gw0](http://gw.tnode.com/) for adding Pandoc reader support.
|
||||||
|
|
||||||
|
|
||||||
|
Why do you need it?
|
||||||
|
===================
|
||||||
|
|
||||||
|
Pelican can generate ToC of reST and Markdown files, using markup's respective
|
||||||
|
directive and extension. Such ToC is generated and placed at the beginning of
|
||||||
|
`article.content` like a string. Consequently it can not be placed anywhere
|
||||||
|
else on the page (eg. `<nav>` HTML5 tag, in header, or at the end of your
|
||||||
|
article's contents).
|
||||||
|
|
||||||
|
To solve this problem, this plugin extracts ToC from `article.content` and
|
||||||
|
places it in its own `article.toc` variable for use in templates.
|
||||||
|
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
============
|
||||||
|
|
||||||
|
`extract_toc` requires BeautifulSoup.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install beautifulsoup4
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
How to Use
|
||||||
|
==========
|
||||||
|
|
||||||
|
This plugin works by extracting the first occurrence of enclosed in:
|
||||||
|
|
||||||
|
- `<div class="toc">` for the default Markdown reader
|
||||||
|
- `<div class="contents topic">` for the default reStructuredText reader
|
||||||
|
- `<nav class="TOC">` for the Pandoc reader
|
||||||
|
|
||||||
|
If ToC appears in your article at more than one places, `extract_toc` will
|
||||||
|
remove only the first occurrence. You shouldn't probably need to have multiple
|
||||||
|
ToC in your article. In case you need to display it multiple times, you can
|
||||||
|
print it via your template.
|
||||||
|
|
||||||
|
|
||||||
|
Template example
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Add something like this to your Pelican templates if missing:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{% if article.toc %}
|
||||||
|
<nav class="toc">
|
||||||
|
{{ article.toc }}
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
reStructuredText reader
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
To add a table of contents to your reStructuredText document (`.rst`) you need to add a `.. contents::` directive to its beginning. See the [docutils 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 reader
|
||||||
|
---------------
|
||||||
|
|
||||||
|
To enable table of contents generation for the Markdown reader you need to set `MD_EXTENSIONS = (['toc'])` in your Pelican configuration file.
|
||||||
|
|
||||||
|
To add a table of contents to your Markdown document (`.md`) you need to place the `[TOC]` marker to its beginning. See the [Python Markdown documentation](http://pythonhosted.org/Markdown/extensions/toc.html) for more details.
|
||||||
|
|
||||||
|
```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.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Pandoc reader
|
||||||
|
-------------
|
||||||
|
|
||||||
|
To enable table of contents generation for the Pandoc reader you need to set `PANDOC_ARGS = (['--toc', '--template=pandoc-template-toc'])` in your Pelican configuration file.
|
||||||
|
|
||||||
|
Contents of the Pandoc template file `pandoc-template-toc.html5`:
|
||||||
|
|
||||||
|
```html
|
||||||
|
$if(toc)$
|
||||||
|
<nav id="TOC">
|
||||||
|
$toc$
|
||||||
|
</nav>
|
||||||
|
$endif$
|
||||||
|
$body$
|
||||||
|
```
|
1
plugins/extract_toc/__init__.py
Normal file
1
plugins/extract_toc/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .extract_toc import *
|
64
plugins/extract_toc/extract_toc.py
Normal file
64
plugins/extract_toc/extract_toc.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Extract Table of Content
|
||||||
|
========================
|
||||||
|
|
||||||
|
A Pelican plugin to extract table of contents (ToC) from `article.content` and
|
||||||
|
place it in its own `article.toc` variable for use in templates.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from os import path
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from pelican import signals, readers, contents
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_toc(content):
|
||||||
|
if isinstance(content, contents.Static):
|
||||||
|
return
|
||||||
|
|
||||||
|
soup = BeautifulSoup(content._content, 'html.parser')
|
||||||
|
filename = content.source_path
|
||||||
|
extension = path.splitext(filename)[1][1:]
|
||||||
|
toc = None
|
||||||
|
|
||||||
|
# default Markdown reader
|
||||||
|
if not toc and readers.MarkdownReader.enabled and extension in readers.MarkdownReader.file_extensions:
|
||||||
|
toc = soup.find('div', class_='toc')
|
||||||
|
if toc:
|
||||||
|
toc.extract()
|
||||||
|
|
||||||
|
# default reStructuredText reader
|
||||||
|
if not toc and readers.RstReader.enabled and extension in readers.RstReader.file_extensions:
|
||||||
|
toc = soup.find('div', class_='contents topic')
|
||||||
|
if toc:
|
||||||
|
toc.extract()
|
||||||
|
tag = BeautifulSoup(str(toc), 'html.parser')
|
||||||
|
tag.div['class'] = 'toc'
|
||||||
|
tag.div['id'] = ''
|
||||||
|
p = tag.find('p', class_='topic-title first')
|
||||||
|
if p:
|
||||||
|
p.extract()
|
||||||
|
toc = tag
|
||||||
|
|
||||||
|
# Pandoc reader (markdown and other formats)
|
||||||
|
if 'pandoc_reader' in content.settings['PLUGINS']:
|
||||||
|
try:
|
||||||
|
from pandoc_reader import PandocReader
|
||||||
|
except ImportError:
|
||||||
|
PandocReader = False
|
||||||
|
if not toc and PandocReader and PandocReader.enabled and extension in PandocReader.file_extensions:
|
||||||
|
toc = soup.find('nav', id='TOC')
|
||||||
|
|
||||||
|
if toc:
|
||||||
|
toc.extract()
|
||||||
|
content._content = soup.decode()
|
||||||
|
content.toc = toc.decode()
|
||||||
|
if content.toc.startswith('<html>'):
|
||||||
|
content.toc = content.toc[12:-14]
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
signals.content_object_init.connect(extract_toc)
|
49
plugins/representative_image/Readme.md
Normal file
49
plugins/representative_image/Readme.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# Summary
|
||||||
|
|
||||||
|
This plugin extracts a representative image (i.e, featured image) from the article's summary or content if not specified in the metadata.
|
||||||
|
|
||||||
|
The plugin also removes any images from the summary after extraction to avoid duplication.
|
||||||
|
|
||||||
|
It allows the flexibility on where and how to display the featured image of an article together with its summary in a template page. For example, the article metadata can be displayed in thumbnail format, in which there is a short summary and an image. The layout of the summary and the image can be varied for aesthetical purpose. It doesn't have to depend on article's content format.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
This plugin requires BeautifulSoup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install beautifulsoup4
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable, add the following to your settings.py:
|
||||||
|
|
||||||
|
```python
|
||||||
|
PLUGIN_PATH = 'path/to/pelican-plugins'
|
||||||
|
PLUGINS = ["representative_image"]
|
||||||
|
```
|
||||||
|
|
||||||
|
`PLUGIN_PATH` can be a path relative to your settings file or an absolute path.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To override the default behavior of selecting the first image in the article's summary or content, set the image property the article's metadata to the URL of the image to display, e.g:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Title: My super title
|
||||||
|
Date: 2010-12-03 10:20
|
||||||
|
Category: Python
|
||||||
|
Tags: pelican, publishing
|
||||||
|
Slug: my-super-post
|
||||||
|
Author: Alexis Metaireau
|
||||||
|
Summary: Short version for index and feeds
|
||||||
|
Image: /images/my-super-image.png
|
||||||
|
|
||||||
|
Article content...
|
||||||
|
```
|
||||||
|
|
||||||
|
To include a representative image in a page add the following to the template:
|
||||||
|
|
||||||
|
```html
|
||||||
|
{% if article.featured_image %}
|
||||||
|
<img src="{{ article.featured_image }}">
|
||||||
|
{% endif %}
|
||||||
|
```
|
1
plugins/representative_image/__init__.py
Normal file
1
plugins/representative_image/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .representative_image import * # noqa
|
58
plugins/representative_image/representative_image.py
Normal file
58
plugins/representative_image/representative_image.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import six
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
from pelican import signals
|
||||||
|
from pelican.contents import Article, Page
|
||||||
|
from pelican.generators import ArticlesGenerator
|
||||||
|
|
||||||
|
|
||||||
|
def images_extraction(instance):
|
||||||
|
representativeImage = None
|
||||||
|
if type(instance) in (Article, Page):
|
||||||
|
if 'image' in instance.metadata:
|
||||||
|
representativeImage = instance.metadata['image']
|
||||||
|
|
||||||
|
# Process Summary:
|
||||||
|
# If summary contains images, extract one to be the representativeImage
|
||||||
|
# and remove images from summary
|
||||||
|
soup = BeautifulSoup(instance.summary, 'html.parser')
|
||||||
|
images = soup.find_all('img')
|
||||||
|
for i in images:
|
||||||
|
if not representativeImage:
|
||||||
|
representativeImage = i['src']
|
||||||
|
i.extract()
|
||||||
|
if len(images) > 0:
|
||||||
|
# set _summary field which is based on metadata. summary field is
|
||||||
|
# only based on article's content and not settable
|
||||||
|
instance._summary = six.text_type(soup)
|
||||||
|
|
||||||
|
# If there are no image in summary, look for it in the content body
|
||||||
|
if not representativeImage:
|
||||||
|
soup = BeautifulSoup(instance._content, 'html.parser')
|
||||||
|
imageTag = soup.find('img')
|
||||||
|
if imageTag:
|
||||||
|
representativeImage = imageTag['src']
|
||||||
|
|
||||||
|
# Set the attribute to content instance
|
||||||
|
instance.featured_image = representativeImage
|
||||||
|
instance.featured_alt = instance.metadata.get('alt', None)
|
||||||
|
instance.featured_link = instance.metadata.get('link', None)
|
||||||
|
instance.featured_caption = instance.metadata.get('caption', None)
|
||||||
|
|
||||||
|
|
||||||
|
def run_plugin(generators):
|
||||||
|
for generator in generators:
|
||||||
|
if isinstance(generator, ArticlesGenerator):
|
||||||
|
for article in generator.articles:
|
||||||
|
images_extraction(article)
|
||||||
|
for translation in article.translations:
|
||||||
|
images_extraction(translation)
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
try:
|
||||||
|
signals.all_generators_finalized.connect(run_plugin)
|
||||||
|
except AttributeError:
|
||||||
|
# NOTE: This results in #314 so shouldn't really be relied on
|
||||||
|
# https://github.com/getpelican/pelican-plugins/issues/314
|
||||||
|
signals.content_object_init.connect(images_extraction)
|
61
plugins/representative_image/test_representative_image.py
Executable file
61
plugins/representative_image/test_representative_image.py
Executable file
@ -0,0 +1,61 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import representative_image
|
||||||
|
from jinja2.utils import generate_lorem_ipsum
|
||||||
|
from pelican.contents import Article
|
||||||
|
|
||||||
|
# Generate content with image
|
||||||
|
TEST_CONTENT_IMAGE_URL = 'https://testimage.com/test.jpg'
|
||||||
|
TEST_CONTENT = str(generate_lorem_ipsum(n=3, html=True)) + '<img src="' + TEST_CONTENT_IMAGE_URL + '"/>'+ str(generate_lorem_ipsum(n=2,html=True)) # noqa
|
||||||
|
TEST_SUMMARY_IMAGE_URL = 'https://testimage.com/summary.jpg'
|
||||||
|
TEST_SUMMARY_WITHOUTIMAGE = str(generate_lorem_ipsum(n=1, html=True))
|
||||||
|
TEST_SUMMARY_WITHIMAGE = TEST_SUMMARY_WITHOUTIMAGE + '<img src="' + TEST_SUMMARY_IMAGE_URL + '"/>' # noqa
|
||||||
|
TEST_CUSTOM_IMAGE_URL = 'https://testimage.com/custom.jpg'
|
||||||
|
|
||||||
|
|
||||||
|
class TestRepresentativeImage(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestRepresentativeImage, self).setUp()
|
||||||
|
representative_image.register()
|
||||||
|
|
||||||
|
def test_extract_image_from_content(self):
|
||||||
|
args = {
|
||||||
|
'content': TEST_CONTENT,
|
||||||
|
'metadata': {
|
||||||
|
'summary': TEST_SUMMARY_WITHOUTIMAGE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
article = Article(**args)
|
||||||
|
self.assertEqual(article.featured_image, TEST_CONTENT_IMAGE_URL)
|
||||||
|
|
||||||
|
def test_extract_image_from_summary(self):
|
||||||
|
args = {
|
||||||
|
'content': TEST_CONTENT,
|
||||||
|
'metadata': {
|
||||||
|
'summary': TEST_SUMMARY_WITHIMAGE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
article = Article(**args)
|
||||||
|
self.assertEqual(article.featured_image, TEST_SUMMARY_IMAGE_URL)
|
||||||
|
self.assertEqual(article.summary, TEST_SUMMARY_WITHOUTIMAGE)
|
||||||
|
|
||||||
|
def test_extract_image_from_summary_with_custom_image(self):
|
||||||
|
args = {
|
||||||
|
'content': TEST_CONTENT,
|
||||||
|
'metadata': {
|
||||||
|
'summary': TEST_SUMMARY_WITHIMAGE,
|
||||||
|
'image': TEST_CUSTOM_IMAGE_URL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
article = Article(**args)
|
||||||
|
self.assertEqual(article.featured_image, TEST_CUSTOM_IMAGE_URL)
|
||||||
|
self.assertEqual(article.summary, TEST_SUMMARY_WITHOUTIMAGE)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
56
plugins/summary/Readme.rst
Normal file
56
plugins/summary/Readme.rst
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
Summary
|
||||||
|
-------
|
||||||
|
|
||||||
|
This plugin allows easy, variable length summaries directly embedded into the
|
||||||
|
body of your articles. It introduces two new settings: ``SUMMARY_BEGIN_MARKER``
|
||||||
|
and ``SUMMARY_END_MARKER``: strings which can be placed directly into an article
|
||||||
|
to mark the beginning and end of a summary. When found, the standard
|
||||||
|
``SUMMARY_MAX_LENGTH`` setting will be ignored. The markers themselves will also
|
||||||
|
be removed from your articles before they are published. The default values
|
||||||
|
are ``<!-- PELICAN_BEGIN_SUMMARY -->`` and ``<!-- PELICAN_END_SUMMARY -->``.
|
||||||
|
For example::
|
||||||
|
|
||||||
|
Title: My super title
|
||||||
|
Date: 2010-12-03 10:20
|
||||||
|
Tags: thats, awesome
|
||||||
|
Category: yeah
|
||||||
|
Slug: my-super-post
|
||||||
|
Author: Alexis Metaireau
|
||||||
|
|
||||||
|
This is the content of my super blog post.
|
||||||
|
<!-- PELICAN_END_SUMMARY -->
|
||||||
|
and this content occurs after the summary.
|
||||||
|
|
||||||
|
Here, the summary is taken to be the first line of the post. Because no
|
||||||
|
beginning marker was found, it starts at the top of the body. It is possible
|
||||||
|
to leave out the end marker instead, in which case the summary will start at the
|
||||||
|
beginning marker and continue to the end of the body.
|
||||||
|
|
||||||
|
If no beginning or end marker is found, and if ``SUMMARY_USE_FIRST_PARAGRAPH``
|
||||||
|
is enabled in the settings, the summary will be the first paragraph of the post.
|
||||||
|
|
||||||
|
The plugin also sets a ``has_summary`` attribute on every article. It is True
|
||||||
|
for articles with an explicitly-defined summary, and False otherwise. (It is
|
||||||
|
also False for an article truncated by ``SUMMARY_MAX_LENGTH``.) Your templates
|
||||||
|
can use this e.g. to add a link to the full text at the end of the summary.
|
||||||
|
|
||||||
|
reST example
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Inserting the markers into a reStructuredText document makes use of the
|
||||||
|
comment directive, because raw HTML is automatically escaped. The reST equivalent of the above Markdown example looks like this::
|
||||||
|
|
||||||
|
My super title
|
||||||
|
##############
|
||||||
|
|
||||||
|
:date: 2010-12-03 10:20
|
||||||
|
:tags: thats, awesome
|
||||||
|
:category: yeah
|
||||||
|
:slug: my-super-post
|
||||||
|
:author: Alexis Metaireau
|
||||||
|
|
||||||
|
This is the content of my super blog post.
|
||||||
|
|
||||||
|
.. PELICAN_END_SUMMARY
|
||||||
|
|
||||||
|
and this content occurs after the summary.
|
1
plugins/summary/__init__.py
Normal file
1
plugins/summary/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .summary import *
|
111
plugins/summary/summary.py
Normal file
111
plugins/summary/summary.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
"""
|
||||||
|
Summary
|
||||||
|
-------
|
||||||
|
|
||||||
|
This plugin allows easy, variable length summaries directly embedded into the
|
||||||
|
body of your articles.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from pelican import signals
|
||||||
|
from pelican.generators import ArticlesGenerator, StaticGenerator, PagesGenerator
|
||||||
|
import re
|
||||||
|
|
||||||
|
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 -->')
|
||||||
|
DEFAULT_CONFIG.setdefault('SUMMARY_USE_FIRST_PARAGRAPH', False)
|
||||||
|
if pelican:
|
||||||
|
pelican.settings.setdefault('SUMMARY_BEGIN_MARKER',
|
||||||
|
'<!-- PELICAN_BEGIN_SUMMARY -->')
|
||||||
|
pelican.settings.setdefault('SUMMARY_END_MARKER',
|
||||||
|
'<!-- PELICAN_END_SUMMARY -->')
|
||||||
|
pelican.settings.setdefault('SUMMARY_USE_FIRST_PARAGRAPH', False)
|
||||||
|
|
||||||
|
def extract_summary(instance):
|
||||||
|
# if summary is already specified, use it
|
||||||
|
# if there is no content, there's nothing to do
|
||||||
|
if hasattr(instance, '_summary') or 'summary' in instance.metadata:
|
||||||
|
instance.has_summary = True
|
||||||
|
return
|
||||||
|
|
||||||
|
if not instance._content:
|
||||||
|
instance.has_summary = False
|
||||||
|
return
|
||||||
|
|
||||||
|
begin_marker = instance.settings['SUMMARY_BEGIN_MARKER']
|
||||||
|
end_marker = instance.settings['SUMMARY_END_MARKER']
|
||||||
|
use_first_paragraph = instance.settings['SUMMARY_USE_FIRST_PARAGRAPH']
|
||||||
|
remove_markers = True
|
||||||
|
|
||||||
|
content = instance._update_content(instance._content, instance.settings['SITEURL'])
|
||||||
|
begin_summary = -1
|
||||||
|
end_summary = -1
|
||||||
|
if begin_marker:
|
||||||
|
begin_summary = content.find(begin_marker)
|
||||||
|
if end_marker:
|
||||||
|
end_summary = content.find(end_marker)
|
||||||
|
|
||||||
|
if begin_summary == -1 and end_summary == -1 and use_first_paragraph:
|
||||||
|
begin_marker, end_marker = '<p>', '</p>'
|
||||||
|
remove_markers = False
|
||||||
|
begin_summary = content.find(begin_marker)
|
||||||
|
end_summary = content.find(end_marker)
|
||||||
|
|
||||||
|
if begin_summary == -1 and end_summary == -1:
|
||||||
|
instance.has_summary = False
|
||||||
|
return
|
||||||
|
|
||||||
|
# skip over the begin marker, if present
|
||||||
|
if begin_summary == -1:
|
||||||
|
begin_summary = 0
|
||||||
|
else:
|
||||||
|
begin_summary = begin_summary + len(begin_marker)
|
||||||
|
|
||||||
|
if end_summary == -1:
|
||||||
|
end_summary = None
|
||||||
|
|
||||||
|
summary = content[begin_summary:end_summary]
|
||||||
|
|
||||||
|
if remove_markers:
|
||||||
|
# remove the markers from the content
|
||||||
|
if begin_summary:
|
||||||
|
content = content.replace(begin_marker, '', 1)
|
||||||
|
if end_summary:
|
||||||
|
content = content.replace(end_marker, '', 1)
|
||||||
|
|
||||||
|
summary = re.sub(r"<div.*>", "", summary)
|
||||||
|
summary = re.sub(r"</div>", "", summary)
|
||||||
|
|
||||||
|
instance._content = content
|
||||||
|
# default_status was added to Pelican Content objects after 3.7.1.
|
||||||
|
# Its use here is strictly to decide on how to set the summary.
|
||||||
|
# There's probably a better way to do this but I couldn't find it.
|
||||||
|
if hasattr(instance, 'default_status'):
|
||||||
|
instance.metadata['summary'] = summary
|
||||||
|
else:
|
||||||
|
instance._summary = summary
|
||||||
|
instance.has_summary = True
|
||||||
|
|
||||||
|
|
||||||
|
def run_plugin(generators):
|
||||||
|
for generator in generators:
|
||||||
|
if isinstance(generator, ArticlesGenerator):
|
||||||
|
for article in generator.articles:
|
||||||
|
extract_summary(article)
|
||||||
|
elif isinstance(generator, PagesGenerator):
|
||||||
|
for page in generator.pages:
|
||||||
|
extract_summary(page)
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
signals.initialized.connect(initialized)
|
||||||
|
try:
|
||||||
|
signals.all_generators_finalized.connect(run_plugin)
|
||||||
|
except AttributeError:
|
||||||
|
# NOTE: This results in #314 so shouldn't really be relied on
|
||||||
|
# https://github.com/getpelican/pelican-plugins/issues/314
|
||||||
|
signals.content_object_init.connect(extract_summary)
|
96
plugins/summary/test_summary.py
Normal file
96
plugins/summary/test_summary.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from jinja2.utils import generate_lorem_ipsum
|
||||||
|
|
||||||
|
# generate one paragraph, enclosed with <p>
|
||||||
|
TEST_CONTENT = str(generate_lorem_ipsum(n=1))
|
||||||
|
TEST_SUMMARY = generate_lorem_ipsum(n=1, html=False)
|
||||||
|
|
||||||
|
|
||||||
|
from pelican.contents import Page
|
||||||
|
import pelican.settings
|
||||||
|
|
||||||
|
import summary
|
||||||
|
|
||||||
|
class TestSummary(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestSummary, self).setUp()
|
||||||
|
pelican.settings.DEFAULT_CONFIG['SUMMARY_MAX_LENGTH'] = None
|
||||||
|
pelican.settings.DEFAULT_CONFIG['SUMMARY_USE_FIRST_PARAGRAPH'] = False
|
||||||
|
|
||||||
|
summary.register()
|
||||||
|
summary.initialized(None)
|
||||||
|
self.page_kwargs = {
|
||||||
|
'content': TEST_CONTENT,
|
||||||
|
'context': {
|
||||||
|
'localsiteurl': '',
|
||||||
|
},
|
||||||
|
'metadata': {
|
||||||
|
'summary': TEST_SUMMARY,
|
||||||
|
'title': 'foo bar',
|
||||||
|
'author': 'Blogger',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _copy_page_kwargs(self):
|
||||||
|
# make a deep copy of page_kwargs
|
||||||
|
page_kwargs = dict([(key, self.page_kwargs[key]) for key in
|
||||||
|
self.page_kwargs])
|
||||||
|
for key in page_kwargs:
|
||||||
|
if not isinstance(page_kwargs[key], dict):
|
||||||
|
break
|
||||||
|
page_kwargs[key] = dict([(subkey, page_kwargs[key][subkey])
|
||||||
|
for subkey in page_kwargs[key]])
|
||||||
|
|
||||||
|
return page_kwargs
|
||||||
|
|
||||||
|
def test_end_summary(self):
|
||||||
|
page_kwargs = self._copy_page_kwargs()
|
||||||
|
del page_kwargs['metadata']['summary']
|
||||||
|
page_kwargs['content'] = (
|
||||||
|
TEST_SUMMARY + '<!-- PELICAN_END_SUMMARY -->' + TEST_CONTENT)
|
||||||
|
page = Page(**page_kwargs)
|
||||||
|
summary.extract_summary(page)
|
||||||
|
# test both the summary and the marker removal
|
||||||
|
self.assertEqual(page.summary, TEST_SUMMARY)
|
||||||
|
self.assertEqual(page.content, TEST_SUMMARY + TEST_CONTENT)
|
||||||
|
|
||||||
|
def test_begin_summary(self):
|
||||||
|
page_kwargs = self._copy_page_kwargs()
|
||||||
|
del page_kwargs['metadata']['summary']
|
||||||
|
page_kwargs['content'] = (
|
||||||
|
'FOOBAR<!-- PELICAN_BEGIN_SUMMARY -->' + TEST_CONTENT)
|
||||||
|
page = Page(**page_kwargs)
|
||||||
|
summary.extract_summary(page)
|
||||||
|
# test both the summary and the marker removal
|
||||||
|
self.assertEqual(page.summary, TEST_CONTENT)
|
||||||
|
self.assertEqual(page.content, 'FOOBAR' + TEST_CONTENT)
|
||||||
|
|
||||||
|
def test_begin_end_summary(self):
|
||||||
|
page_kwargs = self._copy_page_kwargs()
|
||||||
|
del page_kwargs['metadata']['summary']
|
||||||
|
page_kwargs['content'] = (
|
||||||
|
'FOOBAR<!-- PELICAN_BEGIN_SUMMARY -->' + TEST_SUMMARY +
|
||||||
|
'<!-- PELICAN_END_SUMMARY -->' + TEST_CONTENT)
|
||||||
|
page = Page(**page_kwargs)
|
||||||
|
summary.extract_summary(page)
|
||||||
|
# test both the summary and the marker removal
|
||||||
|
self.assertEqual(page.summary, TEST_SUMMARY)
|
||||||
|
self.assertEqual(page.content, 'FOOBAR' + TEST_SUMMARY + TEST_CONTENT)
|
||||||
|
|
||||||
|
def test_use_first_paragraph(self):
|
||||||
|
page_kwargs = self._copy_page_kwargs()
|
||||||
|
del page_kwargs['metadata']['summary']
|
||||||
|
pelican.settings.DEFAULT_CONFIG['SUMMARY_USE_FIRST_PARAGRAPH'] = True
|
||||||
|
page_kwargs['content'] = '<p>' + TEST_SUMMARY + '</p>' + TEST_CONTENT
|
||||||
|
page = Page(**page_kwargs)
|
||||||
|
summary.extract_summary(page)
|
||||||
|
# test both the summary and the marker removal
|
||||||
|
self.assertEqual(page.summary, TEST_SUMMARY)
|
||||||
|
self.assertEqual(page.content, '<p>' + TEST_SUMMARY + '</p>' + TEST_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
Loading…
Reference in New Issue
Block a user