Commits

Kirill Simonov committed 5b9aa4c

Removed dependency on PyRSS2Gen.

Comments (0)

Files changed (6)

 
 * Makes feed entries from Sphinx documents.
 * Generates a list of entries with teasers.
-* Writes the feed in RSS/Atom format.
+* Saves the feed to a file in RSS format.
 * Supports comments via Disqus_.
 
 You can see this extension in action at http://htsql.org/blog/.
     * An annoying bug was fixed.
 
 
-To make a list of news entries and generate an RSS or Atom feed, use
-``feed`` directive::
+To make a list of news entries and generate an RSS file, use ``feed``
+directive::
 
     .. feed::
        :rss: index.rss
-       :atom: index.atom
        :title: Elvensense News
 
        release
 
 The body of ``feed`` directive must list documents containing news
 entries (similar to ``toctree``).  The options of ``feed`` directive
-define the location of RSS and Atom files and describe the feed
-metadata.
+define the name of the RSS file and describe the feed metadata.
 
-You need to manually update your HTML templates to add links to RSS and
-Atom feeds:
+You need to manually update your HTML templates to add a link to the RSS
+feed::
 
       <link rel="alternate"
             type="application/rss+xml"
             title="Elvensense News"
             href="/index.rss" />
-      <link rel="alternate"
-            type="application/atom+xml"
-            title="Elvensense News"
-            href="/index.atom" />
 
 
 Reference
 
     ``rss``
         Where to write the RSS feed (optional).
-    ``atom``
-        Where to write the Atom feed (optional).
     ``title``
-        The name of the feed (for RSS/Atom metadata).
+        The name of the RSS channel.
     ``description``
-        The description of the feed (for RSS/Atom metadata).
+        Description of the RSS channel.
     ``link``
-        The URL of the feed (for RSS/Atom metadata).
+        The website URL.
 
 ``cut``
-    Separates the entry teaser from the main body.
+    Separates the entry teaser from the rest of the text.
 
     This directive has no options and no body.
 
 ``disqus``
     Inserts a Disqus_ comment widget.
 
-    Normally you don't need to use this directive since if
-    ``disqus_shortname`` parameter is set, Disqus comments are included
-    automatically with every feed entry.  This directive allows you to
-    use Disqus with regular Sphinx documents.
+    Normally you don't need to use this directive for news entries
+    since, if ``disqus_shortname`` parameter is set, Disqus comment form
+    is encluded automatically with every feed entry.  This directive
+    allows you to use Disqus with regular Sphinx documents.
 
     Options:
 

demo/_templates/layout.html

+{% extends "!layout.html" %}
+
+{% block linktags %}
+  {{ super() }}
+  <link rel="alternate"
+        type="application/rss+xml"
+        title="Elvensense News"
+        href="/index.rss" />
+{% endblock %}
+
 master_doc = 'index'
 html_theme = 'sphinxdoc'
 highlight_language = 'rest'
-exclude_patterns = ['_build']
+templates_path = ['_templates']
+exclude_patterns = ['_build', '_templates']
 
 
 .. feed::
    :rss: index.rss
-   :atom: index.atom
    :title: Elvensense News
+   :link: http://example.com/
 
    release
    welcome
         'Topic :: Text Processing',
 ]
 PLATFORMS = 'any'
-REQUIRES = ['Sphinx', 'PyRSS2Gen']
+REQUIRES = ['Sphinx']
 PACKAGES = ['sphinxcontrib']
 ZIP_SAFE = False
 INCLUDE_PACKAGE_DATA = True

sphinxcontrib/newsfeed.py

 from docutils.parsers.rst import Directive, directives
 from sphinx import addnodes
 from sphinx.util import docname_join
-import os.path, datetime, re
-import PyRSS2Gen
+import os.path, datetime, collections
 
 
 class FeedDirective(Directive):
     has_content = True
     option_spec = {
             'rss': directives.unchanged,
-            'atom': directives.unchanged,
             'title': directives.unchanged,
             'link': directives.unchanged,
             'description': directives.unchanged,
         subnode = feed()
         subnode['entries'] = includefiles
         subnode['rss'] = self.options.get('rss')
-        subnode['atom'] = self.options.get('atom')
-        subnode['title'] = self.options.get('title', '')
-        subnode['link'] = self.options.get('link', '')
-        subnode['description'] = self.options.get('description', '')
+        subnode['title'] = unicode(self.options.get('title', ''))
+        subnode['link'] = unicode(self.options.get('link', ''))
+        subnode['description'] = unicode(self.options.get('description', ''))
         output.append(subnode)
         return output
 
         if date:
             meta_node += nodes.Text(u' on ')
             date_node = nodes.emphasis(classes=['feed-date'])
-            if date.time():
+            if not date.time():
+                date_node += nodes.Text(date.date())
+            else:
                 date_node += nodes.Text(date)
-            else:
-                date_node += nodes.Text(date.date())
             meta_node += date_node
         meta_node['author'] = author
         meta_node['date'] = date
         node['developer'] = env.config.disqus_developer
         doctree += node
     for node in doctree.traverse(feed):
-        rss_output = node['rss']
+        rss_filename = node['rss']
         rss_title = node['title']
         rss_link = node['link']
         rss_description = node['description']
+        rss_date = datetime.datetime.utcnow()
         rss_items = []
         replacement = []
         for docname in node['entries']:
                 ref_node += title[0]
                 title_node += ref_node
                 section_node += title_node
+                rss_item_title = unicode(title[0])
+                rss_item_link = rss_link+app.builder.get_target_uri(docname)
+                rss_item_description = nodes.compound()
                 for subnode in entry[0]:
                     if isinstance(subnode, (nodes.title, disqus)):
                         continue
                         section_node += para_node
                         break
                     section_node += subnode.deepcopy()
+                    if isinstance(subnode, entrymeta):
+                        continue
+                    rss_item_description += subnode.deepcopy()
                 env.resolve_references(section_node, fromdocname, app.builder)
                 replacement.append(section_node)
-                if rss_output:
-                    rss_item_title = title[0]
-                    rss_item_link = rss_link+app.builder.get_target_uri(docname)
-                    rss_item_description = nodes.compound()
-                    for subnode in entry[0]:
-                        if isinstance(subnode, (nodes.title, entrymeta, disqus)):
-                            continue
-                        if isinstance(subnode, entrycut):
-                            break
-                        rss_item_description += subnode.deepcopy()
-                    env.resolve_references(rss_item_description, docname,
-                                           app.builder)
-                    rss_item_description = app.builder.render_partial(
-                                                    rss_item_description)['body']
-                    rss_item_date = meta['date']
-                    rss_item = PyRSS2Gen.RSSItem(
-                            title = rss_item_title,
-                            link = rss_item_link,
-                            description = rss_item_description,
-                            guid = PyRSS2Gen.Guid(rss_item_link),
-                            pubDate = rss_item_date)
-                    rss_items.append(rss_item)
+                env.resolve_references(rss_item_description, docname, app.builder)
+                rss_item_description = app.builder.render_partial(
+                                                rss_item_description)['body']
+                rss_item_date = meta['date']
+                rss_item = RSSItem(rss_item_title, rss_item_link,
+                                   rss_item_description, rss_item_date)
+                rss_items.append(rss_item)
         node.replace_self(replacement)
-        if rss_output:
-            rss_path = os.path.join(app.builder.outdir, rss_output)
-            rss = PyRSS2Gen.RSS2(
-                    title = rss_title,
-                    link = rss_link,
-                    description = rss_description,
-                    lastBuildDate = datetime.datetime.utcnow(),
-                    items = rss_items)
-            rss.write_xml(open(rss_path, "w"), encoding="utf-8")
+        rss_feed = RSSFeed(rss_title, rss_link, rss_description,
+                           rss_date, rss_items)
+        if rss_filename:
+            rss_path = os.path.join(app.builder.outdir, rss_filename)
+            rss_stream = open(rss_path, 'wb')
+            write_rss(rss_feed, rss_stream)
+            rss_stream.close()
+
+
+RSSFeed = collections.namedtuple('RSSFeed',
+        ['title', 'link', 'description', 'date', 'items'])
+RSSItem = collections.namedtuple('RSSItem',
+        ['title', 'link', 'description', 'date'])
+
+
+def format_text(text):
+    if isinstance(text, unicode):
+        text = text.encode('utf-8')
+    return text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
+
+
+def format_date(date):
+    return "%s, %02d %s %04d %02d:%02d:%02d GMT" % (
+            ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")[date.weekday()],
+            date.day,
+            ("Jan", "Feb", "Mar", "Apr", "May", "Jun",
+             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")[date.month-1],
+                                date.year, date.hour, date.minute, date.second)
+
+
+def write_rss(rss_feed, stream):
+    stream.write('''<?xml version="1.0" encoding="utf-8"?>\n''')
+    stream.write('''<rss version="2.0">\n''')
+    stream.write('''  <channel>\n''')
+    stream.write('''    <title>%s</title>\n'''
+            % format_text(rss_feed.title))
+    stream.write('''    <link>%s</link>\n'''
+            % format_text(rss_feed.link))
+    stream.write('''    <description>%s</description>\n'''
+            % format_text(rss_feed.description))
+    stream.write('''    <lastBuildDate>%s</lastBuildDate>\n'''
+            % format_date(rss_feed.date))
+    stream.write('''    <generator>sphinxcontrib-newsfeed</generator>\n''')
+    for item in rss_feed.items:
+        stream.write('''    <item>\n''')
+        stream.write('''      <title>%s</title>\n'''
+                % format_text(item.title))
+        stream.write('''      <link>%s</link>\n'''
+                % format_text(item.link))
+        stream.write('''      <description>%s</description>\n'''
+                % format_text(item.description))
+        stream.write('''      <guid>%s</guid>\n'''
+                % format_text(item.link))
+        stream.write('''      <pubDate>%s</pubDate>\n'''
+                % format_date(item.date))
+        stream.write('''    </item>\n''')
+    stream.write('''  </channel>\n''')
+    stream.write('''</rss>\n''')
 
 
 def setup(app):