update pelican plugins as they are now used on solar.ltm

master
rra 2021-01-12 13:28:53 +01:00
rodzic 4bf4ca7167
commit 3d0a753f06
16 zmienionych plików z 425 dodań i 74 usunięć

Wyświetl plik

@ -1,8 +1,24 @@
"""
Addressable Paragraphs
------------------------
In converting from MD to html images are wrapped in <p> objects.
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
@ -18,11 +34,12 @@ def content_object_init(instance):
for p in soup(['p', 'object']):
if p.findChild('img'):
p.attrs['class'] = 'img'
caption = soup.new_tag('span',**{'class':'caption'})
for i in reversed(p.contents):
if i.name != 'img': #if it is not an <img> tag
caption.insert(0,i.extract())
p.append(caption)
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()

Wyświetl plik

@ -0,0 +1,2 @@
cssmin
webassets

Wyświetl plik

@ -18,11 +18,11 @@ CUR_DIR = os.path.dirname(__file__)
THEME_DIR = os.path.join(CUR_DIR, 'test_data')
CSS_REF = open(os.path.join(THEME_DIR, 'static', 'css',
'style.min.css')).read()
CSS_HASH = hashlib.md5(CSS_REF).hexdigest()[0:8]
CSS_HASH = hashlib.md5(CSS_REF.encode()).hexdigest()[0:8]
@unittest.skipUnless(module_exists('webassets'), "webassets isn't installed")
@skipIfNoExecutable(['scss', '-v'])
@skipIfNoExecutable(['sass', '-v'])
@skipIfNoExecutable(['cssmin', '--version'])
class TestWebAssets(unittest.TestCase):
"""Base class for testing webassets."""

Wyświetl plik

@ -53,7 +53,7 @@ def _image_path(pelican):
def _out_path(pelican):
return os.path.join(pelican.settings['OUTPUT_PATH'],
pelican.settings.get('DITHER_DIR', DEFAULT_DITHER_DIR))
pelican.settings.get('DITHER_DIR', DEFAULT_DITHER_DIR)).rstrip('/')
def dither(pelican):
global enabled
@ -64,34 +64,35 @@ def dither(pelican):
out_path = _out_path(pelican)
transparency = pelican.settings.get("TRANSPARENCY",DEFAULT_TRANSPARENCY)
STABLE_SITEURL = pelican.settings.get("STABLE_SITEURL")
if not os.path.exists(out_path):
os.mkdir(out_path)
for dirpath, _, filenames in os.walk(in_path):
for filename in filenames:
file_, ext = os.path.splitext(filename)
fn= os.path.join(dirpath,filename)
of = os.path.join(out_path, filename.replace(ext,'.png'))
if not os.path.exists(of) and imghdr.what(fn):
logging.debug("dither plugin: dithering {}".format(fn))
if pelican.settings.get("SITEURL") == STABLE_SITEURL:
for dirpath, _, filenames in os.walk(in_path):
for filename in filenames:
file_, ext = os.path.splitext(filename)
fn= os.path.join(dirpath,filename)
of = os.path.join(out_path, filename.replace(ext,'.png'))
if not os.path.exists(of) and imghdr.what(fn):
logging.debug("Dither plugin: dithering {}".format(fn))
img= Image.open(fn).convert('RGB')
img= Image.open(fn).convert('RGB')
resize = pelican.settings.get('RESIZE', DEFAULT_RESIZE_OUTPUT)
resize = pelican.settings.get('RESIZE', DEFAULT_RESIZE_OUTPUT)
if resize:
image_size = pelican.settings.get('SIZE', DEFAULT_MAX_SIZE)
img.thumbnail(image_size, Image.LANCZOS)
palette = hitherdither.palette.Palette(pelican.settings.get('DITHER_PALETTE', DEFAULT_DITHER_PALETTE))
threshold = pelican.settings.get('THRESHOLD', DEFAULT_THRESHOLD)
img_dithered = hitherdither.ordered.bayer.bayer_dithering(img, palette, threshold, order=8) #see hither dither documentation for different dithering algos
if resize:
image_size = pelican.settings.get('SIZE', DEFAULT_MAX_SIZE)
img.thumbnail(image_size, Image.LANCZOS)
palette = hitherdither.palette.Palette(pelican.settings.get('DITHER_PALETTE', DEFAULT_DITHER_PALETTE))
threshold = pelican.settings.get('THRESHOLD', DEFAULT_THRESHOLD)
img_dithered = hitherdither.ordered.bayer.bayer_dithering(img, palette, threshold, order=8) #see hither dither documentation for different dithering algos
img_dithered.save(of, optimize=True)
#logging.debug(calculate_savings(in_path,out_path))
img_dithered.save(of, optimize=True)
#logging.debug(calculate_savings(in_path,out_path))
def parse_for_images(instance):
#based on better_figures_and_images plugin by @dflock, @phrawzty,@if1live,@jar1karp,@dhalperi,@aqw,@adworacz

Wyświetl plik

@ -1,7 +1,7 @@
Neighbor Articles Plugin for Pelican
====================================
This plugin adds ``next_article`` (newer) and ``prev_article`` (older)
This plugin adds ``next_article`` (newer) and ``prev_article`` (older)
variables to the article's context.
Also adds ``next_article_in_category`` and ``prev_article_in_category``.
@ -27,7 +27,7 @@ Usage
</a>
</li>
{% endif %}
</ul>
</ul>
<ul>
{% if article.prev_article_in_category %}
<li>
@ -43,21 +43,21 @@ Usage
</a>
</li>
{% endif %}
</ul>
</ul>
Usage with the Subcategory plugin
---------------------------------
If you want to get the neigbors within a subcategory it's a little different.
Since an article can belong to more than one subcategory, subcategories are
stored in a list. If you have an article with subcategories like
stored in a list. If you have an article with subcategories like
``Category/Foo/Bar``
it will belong to both subcategory Foo, and Foo/Bar. Subcategory neighbors are
added to an article as ``next_article_in_subcategory#`` and
added to an article as ``next_article_in_subcategory#`` and
``prev_article_in_subcategory#`` where ``#`` is the level of subcategory. So using
the example from above, subcategory1 will be Foo, and subcategory2 Foo/Bar.
the example from above, subcategory1 will be Foo, and subcategory2 Foo/Bar.
Therefor the usage with subcategories is:
.. code-block:: html+jinja
@ -77,7 +77,7 @@ Therefor the usage with subcategories is:
</a>
</li>
{% endif %}
</ul>
</ul>
<ul>
{% if article.prev_article_in_subcategory2 %}
<li>
@ -93,5 +93,5 @@ Therefor the usage with subcategories is:
</a>
</li>
{% endif %}
</ul>
</ul>

Wyświetl plik

@ -3,19 +3,24 @@
Neighbor Articles Plugin for Pelican
====================================
This plugin adds ``next_article`` (newer) and ``prev_article`` (older)
This plugin adds ``next_article`` (newer) and ``prev_article`` (older)
variables to the article's context
"""
from pelican import signals
def iter3(seq):
it = iter(seq)
nxt = None
cur = next(it)
for prv in it:
yield nxt, cur, prv
"""Generate one triplet per element in 'seq' following PEP-479."""
nxt, cur = None, None
for prv in seq:
if cur:
yield nxt, cur, prv
nxt, cur = cur, prv
yield nxt, cur, None
# Don't yield anything if empty seq
if cur:
# Yield last element in seq (also if len(seq) == 1)
yield nxt, cur, None
def get_translation(article, prefered_language):
if not article:
@ -25,6 +30,7 @@ def get_translation(article, prefered_language):
return translation
return article
def set_neighbors(articles, next_name, prev_name):
for nxt, cur, prv in iter3(articles):
exec("cur.{} = nxt".format(next_name))
@ -32,27 +38,29 @@ def set_neighbors(articles, next_name, prev_name):
for translation in cur.translations:
exec(
"translation.{} = get_translation(nxt, translation.lang)".format(
next_name))
"translation.{} = get_translation(nxt, translation.lang)"
.format(next_name))
exec(
"translation.{} = get_translation(prv, translation.lang)".format(
prev_name))
"translation.{} = get_translation(prv, translation.lang)"
.format(prev_name))
def neighbors(generator):
set_neighbors(generator.articles, 'next_article', 'prev_article')
for category, articles in generator.categories:
articles.sort(key=(lambda x: x.date), reverse=(True))
articles.sort(key=lambda x: x.date, reverse=True)
set_neighbors(
articles, 'next_article_in_category', 'prev_article_in_category')
if hasattr(generator, 'subcategories'):
for subcategory, articles in generator.subcategories:
articles.sort(key=(lambda x: x.date), reverse=(True))
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)

Wyświetl plik

@ -0,0 +1,14 @@
Title: Test md File
Category: test
Tags: foo, bar, foobar
Date: 2010-12-02 10:14
Modified: 2010-12-02 10:20
Summary: I have a lot to test
Test Markdown File Header
=========================
Used for pelican test
---------------------
The quick brown fox jumped over the lazy dog's back.

Wyświetl plik

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
from os.path import dirname, join
from tempfile import TemporaryDirectory
from pelican.generators import ArticlesGenerator
from pelican.tests.support import get_settings, unittest
from .neighbors import neighbors
CUR_DIR = dirname(__file__)
class NeighborsTest(unittest.TestCase):
def test_neighbors_basic(self):
with TemporaryDirectory() as tmpdirname:
generator = _build_article_generator(join(CUR_DIR, '..', 'test_data'), tmpdirname)
neighbors(generator)
def test_neighbors_with_single_article(self):
with TemporaryDirectory() as tmpdirname:
generator = _build_article_generator(join(CUR_DIR, 'test_data'), tmpdirname)
neighbors(generator)
def _build_article_generator(content_path, output_path):
settings = get_settings(filenames={})
settings['PATH'] = content_path
context = settings.copy()
context['generated_content'] = dict()
context['static_links'] = set()
article_generator = ArticlesGenerator(
context=context, settings=settings,
path=settings['PATH'], theme=settings['THEME'], output_path=output_path)
article_generator.generate_context()
return article_generator

Wyświetl plik

@ -0,0 +1,32 @@
#Page Meta-Data
A Pelican plugin to add the total page size to each generated page of your site.
It calculates the weight of the HTML page including all image media and returns that in a human readable format (B, KB, MB).
## Caveats:
it currently is tailored to https://solar.lowtechmagazine.com and needs work in the following areas:
* add options to show file name and generation time
* properly handle subsites plugin (currently it only works for dither+subsites)
* make sure it works with --relative-urls flag
* handle static assets
## Use:
To enable the plugin add it to the `PLUGINS` list in `pelicanconf.py`.
Add a div with id `page-size` to your template and `page_metadata` will place the result there.
have fun!
## in case we add generation time:
To use this plugin first import `strftime` at the top of `pelicanconf.py`:
`from time import strftime`
Then add `NOW = strftime('%c')` somewhere in that document as well. This saves the time of generation as a variable that is usable by the `page_metadata` plugin.

Wyświetl plik

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

Wyświetl plik

@ -0,0 +1,138 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- #
# Page Meta-Data
# ------------------------
# Insert meta-data about the generated file into the resulting HMTL.
# Copyright (C) 2019 Roel Roscam Abbing
#
# Support your local Low-Tech Magazine:
# https://solar.lowtechmagazine.com/donate.html
# 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
import os
def get_printable_size(byte_size):
"""
Thanks Pobux!
https://gist.github.com/Pobux/0c474672b3acd4473d459d3219675ad8
"""
BASE_SIZE = 1024.00
MEASURE = ["B", "KB", "MB", "GB", "TB", "PB"]
def _fix_size(size, size_index):
if not size:
return "0"
elif size_index == 0:
return str(size)
else:
return "{:.2f}".format(size)
current_size = byte_size
size_index = 0
while current_size >= BASE_SIZE and len(MEASURE) != size_index:
current_size = current_size / BASE_SIZE
size_index = size_index + 1
size = _fix_size(current_size, size_index)
measure = MEASURE[size_index]
return size + measure
def get_assets(soup):
assets = []
for a in soup.findAll('link', {'rel':['apple-touch-icon','icon','stylesheet']}):
a = a['href'].split('?')[0]
if a not in assets:
assets.append(a)
return assets
def get_media(html_file):
"""
Currently only images because I, for one, am lazy.
"""
html_file = open(html_file).read()
soup = BeautifulSoup(html_file, 'html.parser')
media = []
for img in soup(['img', 'object']):
media.append(img['src'])
featured_images = soup.findAll('div', {'class':'featured-img'})
for fi in featured_images:
fi = fi['style']
start = fi.find("url('")
end = fi.find("');")
url = fi[start+len("url('"):end]
media.append(url)
assets = get_assets(soup)
media = list(set(media+assets)) # duplicate media don't increase page size
return media, soup
def generate_metadata(path, context):
output_path = context['OUTPUT_PATH']
output_file = context['output_file']
siteurl = context['SITEURL']
plugins = context['PLUGINS']
subsites = False
if 'i18n_subsites' in plugins:
subsites = True
lang = context['DEFAULT_LANG']
general_output_path = output_path.replace(lang, '')
siteurl = context['main_siteurl']
media_size = 0
# enumerate all media displayed on the page
media, soup = get_media(path) #reuse the same soup to limit calculation
for m in media:
# filter out SITEURL to prevent trouble
# join output path to file, need to strip any leading slash for os.path
if subsites:
file_name = m.replace(context['main_siteurl']+'/', '')
m = os.path.join(general_output_path, file_name.strip('/'))
else:
file_name = m.replace(context['SITEURL']+'/', '')
m = os.path.join(output_path, file_name.strip('/'))
#print(m)
if os.path.exists(m):
#print(m, 'exists')
media_size = media_size + os.path.getsize(m)
current_file = os.path.join(output_path, output_file)
file_size = os.path.getsize(current_file)
file_size = file_size + media_size
metadata = get_printable_size(file_size)
metadata = get_printable_size(file_size+len(metadata)) # cursed code is cursed
insert_metadata(path, metadata, soup)
def insert_metadata(output_file, metadata, soup):
tag = soup.find('div', {'id':'page-size'})
if tag:
with open(output_file,'w') as f:
tag.string = '{}'.format(metadata)
f.write(str(soup))
def register():
signals.content_written.connect(generate_metadata)

Wyświetl plik

@ -1,29 +1,31 @@
# Summary
This plugin extracts a representative image (i.e, featured image) from the article's summary or content if not specifed in the metadata.
This plugin extracts a representative image (i.e, featured image) from the summary or content of an article or a page, if not specified in the metadata.
The plugin also removes any images from the summary after extraction to avoid duplication.
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.
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
------------
## Installation
This plugin requires BeautifulSoup.
This plugin requires `BeautifulSoup`:
pip install beautifulsoup4
```bash
pip install beautifulsoup4
```
To enable, add the following to your settings.py:
To enable, add the following to your `settings.py`:
PLUGIN_PATH = 'path/to/pelican-plugins'
PLUGINS = ["representative_image"]
```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
-----
## Usage
To override the default behaviour 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:
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
@ -38,8 +40,22 @@ Image: /images/my-super-image.png
Article content...
```
### Page
To include a representative image in a page add the following to the template:
{% if article.featured_image %}
<img src="{{ article.featured_image }}">
{% endif %}
```html
{% if page.featured_image %}
<img src="{{ page.featured_image }}">
{% endif %}
```
### Article
To include a representative image in an article add the following to the template:
```html
{% if article.featured_image %}
<img src="{{ article.featured_image }}">
{% endif %}
```

Wyświetl plik

@ -1 +1 @@
from .representative_image import *
from .representative_image import * # noqa

Wyświetl plik

@ -1,8 +1,9 @@
import six
from bs4 import BeautifulSoup
from pelican import signals
from pelican.contents import Article, Page
from pelican.generators import ArticlesGenerator
from bs4 import BeautifulSoup
from pelican.generators import ArticlesGenerator, PagesGenerator
def images_extraction(instance):
@ -12,7 +13,8 @@ def images_extraction(instance):
representativeImage = instance.metadata['image']
# Process Summary:
# If summary contains images, extract one to be the representativeImage and remove images from 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:
@ -20,7 +22,8 @@ def images_extraction(instance):
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
# 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
@ -32,6 +35,9 @@ def images_extraction(instance):
# 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):
@ -39,6 +45,13 @@ def run_plugin(generators):
if isinstance(generator, ArticlesGenerator):
for article in generator.articles:
images_extraction(article)
for translation in article.translations:
images_extraction(translation)
elif isinstance(generator, PagesGenerator):
for page in generator.pages:
images_extraction(page)
for translation in page.translations:
images_extraction(translation)
def register():

Wyświetl plik

@ -0,0 +1 @@
beautifulsoup4

Wyświetl plik

@ -0,0 +1,73 @@
#!/bin/sh
import unittest
import representative_image
from jinja2.utils import generate_lorem_ipsum
from pelican.contents import Article, Page
# 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)
def test_extract_image_from_page_summary_with_custom_image(self):
args = {
'content': TEST_CONTENT,
'metadata': {
'summary': TEST_SUMMARY_WITHIMAGE,
'image': TEST_CUSTOM_IMAGE_URL,
},
}
page = Page(**args)
self.assertEqual(page.featured_image, TEST_CUSTOM_IMAGE_URL)
self.assertEqual(page.summary, TEST_SUMMARY_WITHOUTIMAGE)
if __name__ == '__main__':
unittest.main()