From 2a564dce1ae412531fc7f6503affc80b44921c13 Mon Sep 17 00:00:00 2001 From: Florian Jacob Date: Wed, 27 Nov 2013 23:00:02 +0100 Subject: [PATCH] Added feed summary plugin. This plugin allows to use the `Summary:` metadata as feed contents. --- feed_summary/Readme.md | 33 +++++++++++++ feed_summary/__init__.py | 1 + feed_summary/feed_summary.py | 57 ++++++++++++++++++++++ feed_summary/magic_set.py | 92 ++++++++++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 feed_summary/Readme.md create mode 100644 feed_summary/__init__.py create mode 100644 feed_summary/feed_summary.py create mode 100644 feed_summary/magic_set.py diff --git a/feed_summary/Readme.md b/feed_summary/Readme.md new file mode 100644 index 0000000..817fcfe --- /dev/null +++ b/feed_summary/Readme.md @@ -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 * + * *Resumption of PR and Maintainer: Florian Jacob ( projects[PLUS]pelican[ÄT]florianjacob.de )* diff --git a/feed_summary/__init__.py b/feed_summary/__init__.py new file mode 100644 index 0000000..93db3be --- /dev/null +++ b/feed_summary/__init__.py @@ -0,0 +1 @@ +from .feed_summary import * diff --git a/feed_summary/feed_summary.py b/feed_summary/feed_summary.py new file mode 100644 index 0000000..0710669 --- /dev/null +++ b/feed_summary/feed_summary.py @@ -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) diff --git a/feed_summary/magic_set.py b/feed_summary/magic_set.py new file mode 100644 index 0000000..bd7ac6a --- /dev/null +++ b/feed_summary/magic_set.py @@ -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 '' % (self.r, self.g, self.b) +>>> c + +>>> @magic_set(color) +... def red(cls): +... return cls(1, 0, 0) +>>> color.red() + +>>> c.red() + +>>> @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() + +