Merge pull request #113 from florianjacob/feed_summary
Add feed summary plugin
This commit is contained in:
33
feed_summary/Readme.md
Normal file
33
feed_summary/Readme.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Feed Summary #
|
||||||
|
This plugin allows article summaries to be used in ATOM and RSS feeds instead of the entire article. It uses the
|
||||||
|
built-in pelican `Summary:` metadata.
|
||||||
|
|
||||||
|
The summary of an article can either be set explicitly with the `Summary:` metadata attribute as described in the
|
||||||
|
[pelican getting started docs](http://docs.getpelican.com/en/latest/getting_started.html#file-metadata),
|
||||||
|
or automatically generated using the number of words specified in the
|
||||||
|
[SUMMARY_MAX_LENGTH](http://docs.getpelican.com/en/latest/settings.html) setting.
|
||||||
|
|
||||||
|
## Usage ##
|
||||||
|
To use this plugin, ensure the following are set in your `pelicanconf.py` file:
|
||||||
|
|
||||||
|
PLUGIN_PATH = '/path/to/pelican-plugins'
|
||||||
|
PLUGINS = [
|
||||||
|
'feed_summary',
|
||||||
|
]
|
||||||
|
'FEED_USE_SUMMARY' = True
|
||||||
|
|
||||||
|
The default value of `'FEED_USE_SUMMARY'` is `False`, so it must be set to `True` to enable the plugin, even if it is loaded.
|
||||||
|
|
||||||
|
This plugin is written for pelican 3.3 and later.
|
||||||
|
|
||||||
|
|
||||||
|
## Implementation Notes ##
|
||||||
|
|
||||||
|
This plugin derives `FeedSummaryWriter` from the `Writer` class, duplicating code of the `Writer._add_item_to_the_feed` method.
|
||||||
|
|
||||||
|
When the `initialized` signal is sent, it alternates the `get_writer` method of the `Pelican` object to use `FeedSummaryWriter` instead of `Writer`.
|
||||||
|
|
||||||
|
A little hackish, but currently this can't be done otherwise via the regular plugin methods.
|
||||||
|
|
||||||
|
* *Initial Code (PR #36): Michelle L. Gill <michelle.lynn.gill@gmail.com>*
|
||||||
|
* *Resumption of PR and Maintainer: Florian Jacob ( projects[PLUS]pelican[ÄT]florianjacob.de )*
|
||||||
1
feed_summary/__init__.py
Normal file
1
feed_summary/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .feed_summary import *
|
||||||
57
feed_summary/feed_summary.py
Normal file
57
feed_summary/feed_summary.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Feed Summary
|
||||||
|
============
|
||||||
|
|
||||||
|
This plugin allows summaries to be used in feeds instead of the full length article.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from jinja2 import Markup
|
||||||
|
|
||||||
|
import six
|
||||||
|
if not six.PY3:
|
||||||
|
from urlparse import urlparse
|
||||||
|
else:
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
from pelican import signals
|
||||||
|
from pelican.writers import Writer
|
||||||
|
from pelican.utils import set_date_tzinfo
|
||||||
|
|
||||||
|
from .magic_set import magic_set
|
||||||
|
|
||||||
|
class FeedSummaryWriter(Writer):
|
||||||
|
def _add_item_to_the_feed(self, feed, item):
|
||||||
|
if self.settings['FEED_USE_SUMMARY']:
|
||||||
|
title = Markup(item.title).striptags()
|
||||||
|
link = '%s/%s' % (self.site_url, item.url)
|
||||||
|
feed.add_item(
|
||||||
|
title=title,
|
||||||
|
link=link,
|
||||||
|
unique_id='tag:%s,%s:%s' % (urlparse(link).netloc,
|
||||||
|
item.date.date(),
|
||||||
|
urlparse(link).path.lstrip('/')),
|
||||||
|
description=item.summary if hasattr(item, 'summary') else item.get_content(self.site_url),
|
||||||
|
categories=item.tags if hasattr(item, 'tags') else None,
|
||||||
|
author_name=getattr(item, 'author', ''),
|
||||||
|
pubdate=set_date_tzinfo(item.modified if hasattr(item, 'modified') else item.date,
|
||||||
|
self.settings.get('TIMEZONE', None)))
|
||||||
|
else:
|
||||||
|
super(FeedSummaryWriter, self)._add_item_to_the_feed(feed, item)
|
||||||
|
|
||||||
|
def set_feed_use_summary_default(pelican_object):
|
||||||
|
# modifying DEFAULT_CONFIG doesn't have any effect at this point in pelican setup
|
||||||
|
# everybody who uses DEFAULT_CONFIG is already used/copied it or uses the pelican_object.settings copy.
|
||||||
|
|
||||||
|
pelican_object.settings.setdefault('FEED_USE_SUMMARY', False)
|
||||||
|
|
||||||
|
def patch_pelican_writer(pelican_object):
|
||||||
|
@magic_set(pelican_object)
|
||||||
|
def get_writer(self):
|
||||||
|
return FeedSummaryWriter(self.output_path,settings=self.settings)
|
||||||
|
|
||||||
|
def register():
|
||||||
|
signals.initialized.connect(set_feed_use_summary_default)
|
||||||
|
signals.initialized.connect(patch_pelican_writer)
|
||||||
92
feed_summary/magic_set.py
Normal file
92
feed_summary/magic_set.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import types
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
# Modifies class methods (or instances of them) on the fly
|
||||||
|
# http://blog.ianbicking.org/2007/08/08/opening-python-classes/
|
||||||
|
# http://svn.colorstudy.com/home/ianb/recipes/magicset.py
|
||||||
|
|
||||||
|
def magic_set(obj):
|
||||||
|
"""
|
||||||
|
Adds a function/method to an object. Uses the name of the first
|
||||||
|
argument as a hint about whether it is a method (``self``), class
|
||||||
|
method (``cls`` or ``klass``), or static method (anything else).
|
||||||
|
Works on both instances and classes.
|
||||||
|
|
||||||
|
>>> class color:
|
||||||
|
... def __init__(self, r, g, b):
|
||||||
|
... self.r, self.g, self.b = r, g, b
|
||||||
|
>>> c = color(0, 1, 0)
|
||||||
|
>>> c # doctest: +ELLIPSIS
|
||||||
|
<__main__.color instance at ...>
|
||||||
|
>>> @magic_set(color)
|
||||||
|
... def __repr__(self):
|
||||||
|
... return '<color %s %s %s>' % (self.r, self.g, self.b)
|
||||||
|
>>> c
|
||||||
|
<color 0 1 0>
|
||||||
|
>>> @magic_set(color)
|
||||||
|
... def red(cls):
|
||||||
|
... return cls(1, 0, 0)
|
||||||
|
>>> color.red()
|
||||||
|
<color 1 0 0>
|
||||||
|
>>> c.red()
|
||||||
|
<color 1 0 0>
|
||||||
|
>>> @magic_set(color)
|
||||||
|
... def name():
|
||||||
|
... return 'color'
|
||||||
|
>>> color.name()
|
||||||
|
'color'
|
||||||
|
>>> @magic_set(c)
|
||||||
|
... def name(self):
|
||||||
|
... return 'red'
|
||||||
|
>>> c.name()
|
||||||
|
'red'
|
||||||
|
>>> @magic_set(c)
|
||||||
|
... def name(cls):
|
||||||
|
... return cls.__name__
|
||||||
|
>>> c.name()
|
||||||
|
'color'
|
||||||
|
>>> @magic_set(c)
|
||||||
|
... def pr(obj):
|
||||||
|
... print obj
|
||||||
|
>>> c.pr(1)
|
||||||
|
1
|
||||||
|
"""
|
||||||
|
def decorator(func):
|
||||||
|
is_class = (isinstance(obj, type)
|
||||||
|
or isinstance(obj, types.ClassType))
|
||||||
|
args, varargs, varkw, defaults = inspect.getargspec(func)
|
||||||
|
if not args or args[0] not in ('self', 'cls', 'klass'):
|
||||||
|
# Static function/method
|
||||||
|
if is_class:
|
||||||
|
replacement = staticmethod(func)
|
||||||
|
else:
|
||||||
|
replacement = func
|
||||||
|
elif args[0] == 'self':
|
||||||
|
if is_class:
|
||||||
|
replacement = func
|
||||||
|
else:
|
||||||
|
def replacement(*args, **kw):
|
||||||
|
return func(obj, *args, **kw)
|
||||||
|
try:
|
||||||
|
replacement.func_name = func.func_name
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if is_class:
|
||||||
|
replacement = classmethod(func)
|
||||||
|
else:
|
||||||
|
def replacement(*args, **kw):
|
||||||
|
return func(obj.__class__, *args, **kw)
|
||||||
|
try:
|
||||||
|
replacement.func_name = func.func_name
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
setattr(obj, func.func_name, replacement)
|
||||||
|
return replacement
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import doctest
|
||||||
|
doctest.testmod()
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user