@ -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. |
@ -0,0 +1 @@ |
|||
from .summary import * |
@ -0,0 +1,105 @@ |
|||
""" |
|||
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'): |
|||
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._content |
|||
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 |
|||
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) |
@ -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() |
@ -0,0 +1,33 @@ |
|||
Thumbnail Creation of images |
|||
============================ |
|||
|
|||
This plugin creates thumbnails for all of the images found under a specific directory, in various thumbnail sizes. |
|||
It requires `PIL` to function properly since `PIL` is used to resize the images, and the thumbnail will only be re-built |
|||
if it doesn't already exist (to save processing time). |
|||
|
|||
Installation |
|||
------------- |
|||
|
|||
Set up like any other plugin, making sure to set `PLUGIN_PATH` and add `thumbnailer` to the `PLUGINS` list. |
|||
|
|||
[PIL](http://www.pythonware.com/products/pil/) or [Pillow](http://pillow.readthedocs.org/en/latest/installation.html#) |
|||
is required. If you choose Pillow, you need to install some additional |
|||
[external libraries](http://www.pythonware.com/products/pil/) to add support for image types like `jpg`. |
|||
|
|||
Configuration |
|||
------------- |
|||
|
|||
* `IMAGE_PATH` is the path to the image directory. It should reside inside your content directory, and defaults to "pictures". |
|||
* `THUMBNAIL_DIR` is the path to the output sub-directory where the thumbnails are generated. |
|||
* `THUMBNAIL_SIZES` is a dictionary mapping size name to size specifications. |
|||
The generated filename will be `originalname_thumbnailname.ext` unless `THUMBNAIL_KEEP_NAME` is set. |
|||
* `THUMBNAIL_KEEP_NAME` is a Boolean that, if set, puts the file with the original name in a thumbnailname folder, named like the key in `THUMBNAIL_SIZES`. |
|||
* `THUMBNAIL_KEEP_TREE` is a Boolean that, if set, saves the image directory tree. |
|||
* `THUMBNAIL_INCLUDE_REGEX` is an optional string that is used as regular expression to restrict thumbnailing to matching files. By default all files not starting with a dot are respected. |
|||
|
|||
Sizes can be specified using any of the following formats: |
|||
|
|||
* wxh will resize to exactly wxh cropping as necessary to get that size |
|||
* wx? will resize so that the width is the specified size, and the height will scale to retain aspect ratio |
|||
* ?xh same as wx? but will height being a set size |
|||
* s is a shorthand for wxh where w=h |
@ -0,0 +1 @@ |
|||
from .thumbnailer import * |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 330 KiB |
After Width: | Height: | Size: 330 KiB |
@ -0,0 +1,63 @@ |
|||
from thumbnailer import _resizer |
|||
from unittest import TestCase, main |
|||
import os |
|||
from PIL import Image |
|||
|
|||
class ThumbnailerTests(TestCase): |
|||
|
|||
def path(self, filename): |
|||
return os.path.join(self.img_path, filename) |
|||
|
|||
def setUp(self): |
|||
self.img_path = os.path.join(os.path.dirname(__file__), "test_data") |
|||
self.img = Image.open(self.path("sample_image.jpg")) |
|||
|
|||
def testSquare(self): |
|||
r = _resizer('square', '100', self.img_path) |
|||
output = r.resize(self.img) |
|||
self.assertEqual((100, 100), output.size) |
|||
|
|||
def testExact(self): |
|||
r = _resizer('exact', '250x100', self.img_path) |
|||
output = r.resize(self.img) |
|||
self.assertEqual((250, 100), output.size) |
|||
|
|||
def testWidth(self): |
|||
r = _resizer('aspect', '250x?', self.img_path) |
|||
output = r.resize(self.img) |
|||
self.assertEqual((250, 166), output.size) |
|||
|
|||
def testHeight(self): |
|||
r = _resizer('aspect', '?x250', self.img_path) |
|||
output = r.resize(self.img) |
|||
self.assertEqual((375, 250), output.size) |
|||
|
|||
class ThumbnailerFilenameTest(TestCase): |
|||
|
|||
def path(self, *parts): |
|||
return os.path.join(self.img_path, *parts) |
|||
|
|||
def setUp(self): |
|||
self.img_path = os.path.join(os.path.dirname(__file__), "test_data") |
|||
|
|||
def testRoot(self): |
|||
"""Test a file that is in the root of img_path.""" |
|||
|
|||
r = _resizer('square', '100', self.img_path) |
|||
new_name = r.get_thumbnail_name(self.path('sample_image.jpg')) |
|||
self.assertEqual('sample_image_square.jpg', new_name) |
|||
|
|||
def testRootWithSlash(self): |
|||
r = _resizer('square', '100', self.img_path + '/') |
|||
new_name = r.get_thumbnail_name(self.path('sample_image.jpg')) |
|||
self.assertEqual('sample_image_square.jpg', new_name) |
|||
|
|||
def testSubdir(self): |
|||
"""Test a file that is in a sub-directory of img_path.""" |
|||
|
|||
r = _resizer('square', '100', self.img_path) |
|||
new_name = r.get_thumbnail_name(self.path('subdir', 'sample_image.jpg')) |
|||
self.assertEqual('subdir/sample_image_square.jpg', new_name) |
|||
|
|||
if __name__=="__main__": |
|||
main() |
@ -0,0 +1,208 @@ |
|||
import os |
|||
import os.path as path |
|||
import re |
|||
from pelican import signals |
|||
|
|||
import logging |
|||
logger = logging.getLogger(__name__) |
|||
|
|||
try: |
|||
from PIL import Image, ImageOps |
|||
enabled = True |
|||
except ImportError: |
|||
logging.warning("Unable to load PIL, disabling thumbnailer") |
|||
enabled = False |
|||
|
|||
DEFAULT_IMAGE_DIR = "pictures" |
|||
DEFAULT_THUMBNAIL_DIR = "thumbnails" |
|||
DEFAULT_THUMBNAIL_SIZES = { |
|||
'thumbnail_square': '150', |
|||
'thumbnail_wide': '150x?', |
|||
'thumbnail_tall': '?x150', |
|||
} |
|||
DEFAULT_TEMPLATE = """<a href="{url}" rel="shadowbox" title="{filename}"><img src="{thumbnail}" alt="{filename}"></a>""" |
|||
DEFAULT_GALLERY_THUMB = "thumbnail_square" |
|||
|
|||
class _resizer(object): |
|||
""" Resizes based on a text specification, see readme """ |
|||
|
|||
REGEX = re.compile(r'(\d+|\?)x(\d+|\?)') |
|||
|
|||
def __init__(self, name, spec, root): |
|||
self._name = name |
|||
self._spec = spec |
|||
# The location of input images from _image_path. |
|||
self._root = root |
|||
|
|||
def _null_resize(self, w, h, image): |
|||
return image |
|||
|
|||
def _exact_resize(self, w, h, image): |
|||
retval = ImageOps.fit(image, (w,h), Image.BICUBIC) |
|||
return retval |
|||
|
|||
def _aspect_resize(self, w, h, image): |
|||
retval = image.copy() |
|||
retval.thumbnail((w, h), Image.ANTIALIAS) |
|||
|
|||
return retval |
|||
|
|||
def resize(self, image): |
|||
resizer = self._null_resize |
|||
|
|||
# Square resize and crop |
|||
if 'x' not in self._spec: |
|||
resizer = self._exact_resize |
|||
targetw = int(self._spec) |
|||
targeth = targetw |
|||
else: |
|||
matches = self.REGEX.search(self._spec) |
|||
tmpw = matches.group(1) |
|||
tmph = matches.group(2) |
|||
|
|||
# Full Size |
|||
if tmpw == '?' and tmph == '?': |
|||
targetw = image.size[0] |
|||
targeth = image.size[1] |
|||
resizer = self._null_resize |
|||
|
|||
# Set Height Size |
|||
if tmpw == '?': |
|||
targetw = image.size[0] |
|||
targeth = int(tmph) |
|||
resizer = self._aspect_resize |
|||
|
|||
# Set Width Size |
|||
elif tmph == '?': |
|||
targetw = int(tmpw) |
|||
targeth = image.size[1] |
|||
resizer = self._aspect_resize |
|||
|
|||
# Scale and Crop |
|||
else: |
|||
targetw = int(tmpw) |
|||
targeth = int(tmph) |
|||
resizer = self._exact_resize |
|||
|
|||
logging.debug("Using resizer {0}".format(resizer.__name__)) |
|||
return resizer(targetw, targeth, image) |
|||
|
|||
def get_thumbnail_name(self, in_path): |
|||
# Find the partial path + filename beyond the input image directory. |
|||
prefix = path.commonprefix([in_path, self._root]) |
|||
new_filename = in_path[len(prefix) + 1:] |
|||
|
|||
# Generate the new filename. |
|||
(basename, ext) = path.splitext(new_filename) |
|||
return "{0}_{1}{2}".format(basename, self._name, ext) |
|||
|
|||
def resize_file_to(self, in_path, out_path, keep_filename=False): |
|||
""" Given a filename, resize and save the image per the specification into out_path |
|||
|
|||
:param in_path: path to image file to save. Must be supported by PIL |
|||
:param out_path: path to the directory root for the outputted thumbnails to be stored |
|||
:return: None |
|||
""" |
|||
if keep_filename: |
|||
filename = path.join(out_path, path.basename(in_path)) |
|||
else: |
|||
filename = path.join(out_path, self.get_thumbnail_name(in_path)) |
|||
out_path = path.dirname(filename) |
|||
if not path.exists(out_path): |
|||
os.makedirs(out_path) |
|||
if not path.exists(filename): |
|||
try: |
|||
image = Image.open(in_path) |
|||
thumbnail = self.resize(image) |
|||
thumbnail.save(filename) |
|||
logger.info("Generated Thumbnail {0}".format(path.basename(filename))) |
|||
except IOError: |
|||
logger.info("Generating Thumbnail for {0} skipped".format(path.basename(filename))) |
|||
|
|||
|
|||
def resize_thumbnails(pelican): |
|||
""" Resize a directory tree full of images into thumbnails |
|||
|
|||
:param pelican: The pelican instance |
|||
:return: None |
|||
""" |
|||
global enabled |
|||
if not enabled: |
|||
return |
|||
|
|||
in_path = _image_path(pelican) |
|||
|
|||
include_regex = pelican.settings.get('THUMBNAIL_INCLUDE_REGEX') |
|||
if include_regex: |
|||
pattern = re.compile(include_regex) |
|||
is_included = lambda name: pattern.match(name) |
|||
else: |
|||
is_included = lambda name: not name.startswith('.') |
|||
|
|||
sizes = pelican.settings.get('THUMBNAIL_SIZES', DEFAULT_THUMBNAIL_SIZES) |
|||
resizers = dict((k, _resizer(k, v, in_path)) for k,v in sizes.items()) |
|||
logger.debug("Thumbnailer Started") |
|||
for dirpath, _, filenames in os.walk(in_path): |
|||
for filename in filenames: |
|||
if is_included(filename): |
|||
for name, resizer in resizers.items(): |
|||
in_filename = path.join(dirpath, filename) |
|||
out_path = get_out_path(pelican, in_path, in_filename, name) |
|||
resizer.resize_file_to( |
|||
in_filename, |
|||
out_path, pelican.settings.get('THUMBNAIL_KEEP_NAME')) |
|||
|
|||
|
|||
def get_out_path(pelican, in_path, in_filename, name): |
|||
base_out_path = path.join(pelican.settings['OUTPUT_PATH'], |
|||
pelican.settings.get('THUMBNAIL_DIR', DEFAULT_THUMBNAIL_DIR)) |
|||
logger.debug("Processing thumbnail {0}=>{1}".format(in_filename, name)) |
|||
if pelican.settings.get('THUMBNAIL_KEEP_NAME', False): |
|||
if pelican.settings.get('THUMBNAIL_KEEP_TREE', False): |
|||
return path.join(base_out_path, name, path.dirname(path.relpath(in_filename, in_path))) |
|||
else: |
|||
return path.join(base_out_path, name) |
|||
else: |
|||
return base_out_path |
|||
|
|||
|
|||
def _image_path(pelican): |
|||
return path.join(pelican.settings['PATH'], |
|||
pelican.settings.get("IMAGE_PATH", DEFAULT_IMAGE_DIR)).rstrip('/') |
|||
|
|||
|
|||
def expand_gallery(generator, metadata): |
|||
""" Expand a gallery tag to include all of the files in a specific directory under IMAGE_PATH |
|||
|
|||
:param pelican: The pelican instance |
|||
:return: None |
|||
""" |
|||
if "gallery" not in metadata or metadata['gallery'] is None: |
|||
return # If no gallery specified, we do nothing |
|||
|
|||
lines = [ ] |
|||
base_path = _image_path(generator) |
|||
in_path = path.join(base_path, metadata['gallery']) |
|||
template = generator.settings.get('GALLERY_TEMPLATE', DEFAULT_TEMPLATE) |
|||
thumbnail_name = generator.settings.get("GALLERY_THUMBNAIL", DEFAULT_GALLERY_THUMB) |
|||
thumbnail_prefix = generator.settings.get("") |
|||
resizer = _resizer(thumbnail_name, '?x?', base_path) |
|||
for dirpath, _, filenames in os.walk(in_path): |
|||
for filename in filenames: |
|||
if not filename.startswith('.'): |
|||
url = path.join(dirpath, filename).replace(base_path, "")[1:] |
|||
url = path.join('/static', generator.settings.get('IMAGE_PATH', DEFAULT_IMAGE_DIR), url).replace('\\', '/') |
|||
logger.debug("GALLERY: {0}".format(url)) |
|||
thumbnail = resizer.get_thumbnail_name(filename) |
|||
thumbnail = path.join('/', generator.settings.get('THUMBNAIL_DIR', DEFAULT_THUMBNAIL_DIR), thumbnail).replace('\\', '/') |
|||
lines.append(template.format( |
|||
filename=filename, |
|||
url=url, |
|||
thumbnail=thumbnail, |
|||
)) |
|||
metadata['gallery_content'] = "\n".join(lines) |
|||
|
|||
|
|||
def register(): |
|||
signals.finalized.connect(resize_thumbnails) |
|||
signals.article_generator_context.connect(expand_gallery) |
@ -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. |
@ -0,0 +1 @@ |
|||
from .summary import * |
@ -0,0 +1,105 @@ |
|||
""" |
|||
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'): |
|||
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._content |
|||
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 |
|||
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) |
@ -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() |
@ -0,0 +1,33 @@ |
|||
Thumbnail Creation of images |
|||
============================ |
|||
|
|||
This plugin creates thumbnails for all of the images found under a specific directory, in various thumbnail sizes. |
|||
It requires `PIL` to function properly since `PIL` is used to resize the images, and the thumbnail will only be re-built |
|||
if it doesn't already exist (to save processing time). |
|||
|
|||
Installation |
|||
------------- |
|||
|
|||
Set up like any other plugin, making sure to set `PLUGIN_PATH` and add `thumbnailer` to the `PLUGINS` list. |
|||
|
|||
[PIL](http://www.pythonware.com/products/pil/) or [Pillow](http://pillow.readthedocs.org/en/latest/installation.html#) |
|||
is required. If you choose Pillow, you need to install some additional |
|||
[external libraries](http://www.pythonware.com/products/pil/) to add support for image types like `jpg`. |
|||
|
|||
Configuration |
|||
------------- |
|||
|
|||
* `IMAGE_PATH` is the path to the image directory. It should reside inside your content directory, and defaults to "pictures". |
|||
* `THUMBNAIL_DIR` is the path to the output sub-directory where the thumbnails are generated. |
|||
* `THUMBNAIL_SIZES` is a dictionary mapping size name to size specifications. |
|||
The generated filename will be `originalname_thumbnailname.ext` unless `THUMBNAIL_KEEP_NAME` is set. |
|||
* `THUMBNAIL_KEEP_NAME` is a Boolean that, if set, puts the file with the original name in a thumbnailname folder, named like the key in `THUMBNAIL_SIZES`. |
|||
* `THUMBNAIL_KEEP_TREE` is a Boolean that, if set, saves the image directory tree. |
|||
* `THUMBNAIL_INCLUDE_REGEX` is an optional string that is used as regular expression to restrict thumbnailing to matching files. By default all files not starting with a dot are respected. |
|||
|
|||
Sizes can be specified using any of the following formats: |
|||
|
|||
* wxh will resize to exactly wxh cropping as necessary to get that size |
|||
* wx? will resize so that the width is the specified size, and the height will scale to retain aspect ratio |
|||
* ?xh same as wx? but will height being a set size |
|||
* s is a shorthand for wxh where w=h |
@ -0,0 +1 @@ |
|||
from .thumbnailer import * |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 330 KiB |
After Width: | Height: | Size: 330 KiB |
@ -0,0 +1,63 @@ |
|||
from thumbnailer import _resizer |
|||
from unittest import TestCase, main |
|||
import os |
|||
from PIL import Image |
|||
|
|||
class ThumbnailerTests(TestCase): |
|||
|
|||
def path(self, filename): |
|||
return os.path.join(self.img_path, filename) |
|||
|
|||
def setUp(self): |
|||
self.img_path = os.path.join(os.path.dirname(__file__), "test_data") |
|||
self.img = Image.open(self.path("sample_image.jpg")) |
|||
|
|||
def testSquare(self): |
|||
r = _resizer('square', '100', self.img_path) |
|||
output = r.resize(self.img) |
|||
self.assertEqual((100, 100), output.size) |
|||
|
|||
def testExact(self): |
|||
r = _resizer('exact', '250x100', self.img_path) |
|||
output = r.resize(self.img) |
|||
self.assertEqual((250, 100), output.size) |
|||
|
|||
def testWidth(self): |
|||
r = _resizer('aspect', '250x?', self.img_path) |
|||
output = r.resize(self.img) |
|||
self.assertEqual((250, 166), output.size) |
|||
|
|||
def testHeight(self): |
|||
r = _resizer('aspect', '?x250', self.img_path) |
|||
output = r.resize(self.img) |
|||
self.assertEqual((375, 250), output.size) |
|||
|
|||
class ThumbnailerFilenameTest(TestCase): |
|||
|
|||
def path(self, *parts): |
|||
return os.path.join(self.img_path, *parts) |
|||
|
|||
def setUp(self): |
|||
self.img_path = os.path.join(os.path.dirname(__file__), "test_data") |
|||
|
|||
def testRoot(self): |
|||
"""Test a file that is in the root of img_path.""" |
|||
|
|||
r = _resizer('square', '100', self.img_path) |
|||
new_name = r.get_thumbnail_name(self.path('sample_image.jpg')) |
|||
self.assertEqual('sample_image_square.jpg', new_name) |
|||
|
|||
def testRootWithSlash(self): |
|||
r = _resizer('square', '100', self.img_path + '/') |
|||
new_name = r.get_thumbnail_name(self.path('sample_image.jpg')) |
|||
self.assertEqual('sample_image_square.jpg', new_name) |
|||
|
|||
def testSubdir(self): |
|||
"""Test a file that is in a sub-directory of img_path.""" |
|||
|
|||
r = _resizer('square', '100', self.img_path) |
|||
new_name = r.get_thumbnail_name(self.path('subdir', 'sample_image.jpg')) |
|||
self.assertEqual('subdir/sample_image_square.jpg', new_name) |
|||
|
|||
if __name__=="__main__": |
|||
main() |
@ -0,0 +1,208 @@ |
|||
import os |
|||
import os.path as path |
|||
import re |
|||
from pelican import signals |
|||
|
|||
import logging |
|||
logger = logging.getLogger(__name__) |
|||
|
|||
try: |
|||
from PIL import Image, ImageOps |
|||
enabled = True |
|||
except ImportError: |
|||
logging.warning("Unable to load PIL, disabling thumbnailer") |
|||
enabled = False |
|||
|
|||
DEFAULT_IMAGE_DIR = "pictures" |
|||
DEFAULT_THUMBNAIL_DIR = "thumbnails" |
|||
DEFAULT_THUMBNAIL_SIZES = { |
|||
'thumbnail_square': '150', |
|||
'thumbnail_wide': '150x?', |
|||
'thumbnail_tall': '?x150', |
|||
} |
|||
DEFAULT_TEMPLATE = """<a href="{url}" rel="shadowbox" title="{filename}"><img src="{thumbnail}" alt="{filename}"></a>""" |
|||
DEFAULT_GALLERY_THUMB = "thumbnail_square" |
|||
|
|||
class _resizer(object): |
|||
""" Resizes based on a text specification, see readme """ |
|||
|
|||
REGEX = re.compile(r'(\d+|\?)x(\d+|\?)') |
|||
|
|||
def __init__(self, name, spec, root): |
|||
self._name = name |
|||
self._spec = spec |
|||
# The location of input images from _image_path. |
|||
self._root = root |
|||
|
|||
def _null_resize(self, w, h, image): |
|||
return image |
|||
|
|||
def _exact_resize(self, w, h, image): |
|||
retval = ImageOps.fit(image, (w,h), Image.BICUBIC) |
|||
return retval |
|||
|
|||
def _aspect_resize(self, w, h, image): |
|||
retval = image.copy() |
|||
retval.thumbnail((w, h), Image.ANTIALIAS) |
|||
|
|||
return retval |
|||
|
|||
def resize(self, image): |
|||
resizer = self._null_resize |
|||
|
|||
# Square resize and crop |
|||
if 'x' not in self._spec: |
|||
resizer = self._exact_resize |
|||
targetw = int(self._spec) |
|||
targeth = targetw |
|||
else: |
|||
matches = self.REGEX.search(self._spec) |
|||
tmpw = matches.group(1) |
|||
tmph = matches.group(2) |
|||
|
|||
# Full Size |
|||
if tmpw == '?' and tmph == '?': |
|||
targetw = image.size[0] |
|||
targeth = image.size[1] |
|||
resizer = self._null_resize |
|||
|
|||
# Set Height Size |
|||
if tmpw == '?': |
|||
targetw = image.size[0] |
|||
targeth = int(tmph) |
|||
resizer = self._aspect_resize |
|||
|
|||
# Set Width Size |
|||
elif tmph == '?': |
|||
targetw = int(tmpw) |
|||
targeth = image.size[1] |
|||
resizer = self._aspect_resize |
|||
|
|||
# Scale and Crop |
|||
else: |
|||
targetw = int(tmpw) |
|||
targeth = int(tmph) |
|||
resizer = self._exact_resize |
|||
|
|||
logging.debug("Using resizer {0}".format(resizer.__name__)) |
|||
return resizer(targetw, targeth, image) |
|||
|
|||
def get_thumbnail_name(self, in_path): |
|||
# Find the partial path + filename beyond the input image directory. |
|||
prefix = path.commonprefix([in_path, self._root]) |
|||
new_filename = in_path[len(prefix) + 1:] |
|||
|
|||
# Generate the new filename. |
|||
(basename, ext) = path.splitext(new_filename) |
|||
return "{0}_{1}{2}".format(basename, self._name, ext) |
|||
|
|||
def resize_file_to(self, in_path, out_path, keep_filename=False): |
|||
""" Given a filename, resize and save the image per the specification into out_path |
|||
|
|||
:param in_path: path to image file to save. Must be supported by PIL |
|||
:param out_path: path to the directory root for the outputted thumbnails to be stored |
|||
:return: None |
|||
""" |
|||
if keep_filename: |
|||
filename = path.join(out_path, path.basename(in_path)) |
|||
else: |
|||
filename = path.join(out_path, self.get_thumbnail_name(in_path)) |
|||
out_path = path.dirname(filename) |
|||
if not path.exists(out_path): |
|||
os.makedirs(out_path) |
|||
if not path.exists(filename): |
|||
try: |
|||
image = Image.open(in_path) |
|||
thumbnail = self.resize(image) |
|||
thumbnail.save(filename) |
|||
logger.info("Generated Thumbnail {0}".format(path.basename(filename))) |
|||
except IOError: |
|||
logger.info("Generating Thumbnail for {0} skipped".format(path.basename(filename))) |
|||
|
|||
|
|||
def resize_thumbnails(pelican): |
|||
""" Resize a directory tree full of images into thumbnails |
|||
|
|||
:param pelican: The pelican instance |
|||
:return: None |
|||
""" |
|||
global enabled |
|||
if not enabled: |
|||
return |
|||
|
|||
in_path = _image_path(pelican) |
|||
|
|||
include_regex = pelican.settings.get('THUMBNAIL_INCLUDE_REGEX') |
|||
if include_regex: |
|||
pattern = re.compile(include_regex) |
|||
is_included = lambda name: pattern.match(name) |
|||
else: |
|||
is_included = lambda name: not name.startswith('.') |
|||
|
|||
sizes = pelican.settings.get('THUMBNAIL_SIZES', DEFAULT_THUMBNAIL_SIZES) |
|||
resizers = dict((k, _resizer(k, v, in_path)) for k,v in sizes.items()) |
|||
logger.debug("Thumbnailer Started") |
|||
for dirpath, _, filenames in os.walk(in_path): |
|||
for filename in filenames: |
|||
if is_included(filename): |
|||
for name, resizer in resizers.items(): |
|||
in_filename = path.join(dirpath, filename) |
|||
out_path = get_out_path(pelican, in_path, in_filename, name) |
|||
resizer.resize_file_to( |
|||
in_filename, |
|||
out_path, pelican.settings.get('THUMBNAIL_KEEP_NAME')) |
|||
|
|||
|
|||
def get_out_path(pelican, in_path, in_filename, name): |
|||
base_out_path = path.join(pelican.settings['OUTPUT_PATH'], |
|||
pelican.settings.get('THUMBNAIL_DIR', DEFAULT_THUMBNAIL_DIR)) |
|||
logger.debug("Processing thumbnail {0}=>{1}".format(in_filename, name)) |
|||
if pelican.settings.get('THUMBNAIL_KEEP_NAME', False): |
|||
if pelican.settings.get('THUMBNAIL_KEEP_TREE', False): |
|||
return path.join(base_out_path, name, path.dirname(path.relpath(in_filename, in_path))) |
|||
else: |
|||
return path.join(base_out_path, name) |
|||
else: |
|||
return base_out_path |
|||
|
|||
|
|||
def _image_path(pelican): |
|||
return path.join(pelican.settings['PATH'], |
|||
pelican.settings.get("IMAGE_PATH", DEFAULT_IMAGE_DIR)).rstrip('/') |
|||
|
|||
|
|||
def expand_gallery(generator, metadata): |
|||
""" Expand a gallery tag to include all of the files in a specific directory under IMAGE_PATH |
|||
|
|||
:param pelican: The pelican instance |
|||
:return: None |
|||
""" |
|||
if "gallery" not in metadata or metadata['gallery'] is None: |
|||
return # If no gallery specified, we do nothing |
|||
|
|||
lines = [ ] |
|||
base_path = _image_path(generator) |
|||
in_path = path.join(base_path, metadata['gallery']) |
|||
template = generator.settings.get('GALLERY_TEMPLATE', DEFAULT_TEMPLATE) |
|||
thumbnail_name = generator.settings.get("GALLERY_THUMBNAIL", DEFAULT_GALLERY_THUMB) |
|||
thumbnail_prefix = generator.settings.get("") |
|||
resizer = _resizer(thumbnail_name, '?x?', base_path) |
|||
for dirpath, _, filenames in os.walk(in_path): |
|||
for filename in filenames: |
|||
if not filename.startswith('.'): |
|||
url = path.join(dirpath, filename).replace(base_path, "")[1:] |
|||
url = path.join('/static', generator.settings.get('IMAGE_PATH', DEFAULT_IMAGE_DIR), url).replace('\\', '/') |
|||
logger.debug("GALLERY: {0}".format(url)) |
|||
thumbnail = resizer.get_thumbnail_name(filename) |
|||
thumbnail = path.join('/', generator.settings.get('THUMBNAIL_DIR', DEFAULT_THUMBNAIL_DIR), thumbnail).replace('\\', '/') |
|||
lines.append(template.format( |
|||
filename=filename, |
|||
url=url, |
|||
thumbnail=thumbnail, |
|||
)) |
|||
metadata['gallery_content'] = "\n".join(lines) |
|||
|
|||
|
|||
def register(): |
|||
signals.finalized.connect(resize_thumbnails) |
|||
signals.article_generator_context.connect(expand_gallery) |