# HG changeset patch # User Paul Boddie # Date 1267912287 -3600 # Node ID 5929e8a9b8dd81535ae162b4b1ee70d0dd7f6ec4 # Parent a14949a7654a22abde9e6d2c38dd13ea44f1cef2 Added initial support for importing events from RSS feeds. Introduced a support function for completing pages using templates and event details, shared by the event feed reader script and the new event action. Updated release information. diff -r a14949a7654a -r 5929e8a9b8dd EventAggregatorSupport.py --- a/EventAggregatorSupport.py Sat Feb 27 01:43:30 2010 +0100 +++ b/EventAggregatorSupport.py Sat Mar 06 22:51:27 2010 +0100 @@ -21,7 +21,7 @@ except NameError: from sets import Set as set -__version__ = "0.5" +__version__ = "0.6" # Date labels. @@ -399,7 +399,7 @@ # Text which need not be quoted, but it will be Wiki text. - elif term in ("description",): + elif term in ("description", "link"): desc = event_details[term] replaced_terms.add(term) @@ -879,7 +879,7 @@ def getDate(s): - "Parse the string 's', extracting and returning a date string." + "Parse the string 's', extracting and returning a date object." m = date_regexp.search(s) if m: @@ -887,9 +887,21 @@ else: return None +def getDateStrings(s): + + "Parse the string 's', extracting and returning all date strings." + + start = 0 + m = date_regexp.search(s, start) + l = [] + while m: + l.append("-".join(m.groups())) + m = date_regexp.search(s, m.end()) + return l + def getMonth(s): - "Parse the string 's', extracting and returning a month string." + "Parse the string 's', extracting and returning a month object." m = month_regexp.search(s) if m: @@ -1013,4 +1025,35 @@ else: return page.link_to_raw(request, text, query_string) +def getFullPageName(parent, title): + + """ + Return a full page name from the given 'parent' page (can be empty or None) + and 'title' (a simple page name). + """ + + if parent: + return "%s/%s" % (parent.rstrip("/"), title) + else: + return title + +def fillEventPageFromTemplate(template_page, new_page, event_details, category_pagenames): + + """ + Using the given 'template_page', complete the 'new_page' by copying the + template and adding the given 'event_details' (a dictionary of event + fields), setting also the 'category_pagenames' to define category + membership. + """ + + event_page = EventPage(template_page) + new_event_page = EventPage(new_page) + new_event_page.copyPage(event_page) + + if new_event_page.getFormat() == "wiki": + new_event = Event(new_event_page, event_details) + new_event_page.setEvents([new_event]) + new_event_page.setCategoryMembership(category_pagenames) + new_event_page.saveChanges() + # vim: tabstop=4 expandtab shiftwidth=4 diff -r a14949a7654a -r 5929e8a9b8dd PKG-INFO --- a/PKG-INFO Sat Feb 27 01:43:30 2010 +0100 +++ b/PKG-INFO Sat Mar 06 22:51:27 2010 +0100 @@ -1,12 +1,12 @@ Metadata-Version: 1.1 Name: EventAggregator -Version: 0.5 +Version: 0.6 Author: Paul Boddie Author-email: paul at boddie org uk Maintainer: Paul Boddie Maintainer-email: paul at boddie org uk Home-page: http://moinmo.in/MacroMarket/EventAggregator -Download-url: http://moinmo.in/MacroMarket/EventAggregator?action=AttachFile&do=view&target=EventAggregator-0.5.tar.gz +Download-url: http://moinmo.in/MacroMarket/EventAggregator?action=AttachFile&do=view&target=EventAggregator-0.6.tar.gz Summary: Aggregate event data and display it in an event calendar (or summarise it in iCalendar and RSS resources) License: GPL (version 2 or later) Description: The EventAggregator macro for MoinMoin can be used to display event diff -r a14949a7654a -r 5929e8a9b8dd README.txt --- a/README.txt Sat Feb 27 01:43:30 2010 +0100 +++ b/README.txt Sat Mar 06 22:51:27 2010 +0100 @@ -20,6 +20,9 @@ categories to which the page will be assigned, and the start and end dates of the event. +The eventfeed script can be used to import events from RSS feeds, inserting +new pages into a Wiki. + Important Notices ----------------- @@ -27,8 +30,8 @@ links have been fixed to return only events within the specified period and to work with day- and month-relative calendars. Users who have bookmarks in their Web browser or feed reader should replace these bookmarks by visiting the -bookmarked page and acquiring new versions of these links, once EventAggregator -has been upgraded. +bookmarked page and acquiring new versions of these links, once +EventAggregator has been upgraded. Installation ------------ @@ -77,12 +80,24 @@ @import "event-aggregator-print.css"; @import "event-aggregator.css"; -To install the actions in a Wiki, consider using the instactions script provided: +To install the actions in a Wiki, consider using the instactions script +provided: ./instactions path-to-wiki On non-UNIX platforms, it is necessary to manually copy the contents of the -actions directory in this distribution into the actions directory of your Wiki. +actions directory in this distribution into the actions directory of your +Wiki. + +To install the eventfeed script which can import events from RSS feeds, use +the instscripts script, making sure to set your PYTHONPATH so that the script +can find your MoinMoin installation: + + PYTHONPATH=path-to-site-packages ./instscripts + +This script should work on UNIX and non-UNIX systems, although the above +example demonstrates setting the PYTHONPATH in the bash shell on UNIX-like +systems. Useful Pages ------------ @@ -132,6 +147,30 @@ See pages/HelpOnEventAggregatorNewEvent for more detailed information. +Running the Scripts +------------------- + +To import events from an RSS feed, the eventfeed script integrated with the +moin program can be used as follows: + +moin --config-dir=path-to-wiki --wiki-url=example.com/ \ + import eventfeed --url=url-of-events-feed + +Thus, to import events from the FSFE events RSS feed, the following command +could be used: + +moin --config-dir=path-to-wiki --wiki-url=example.com/ \ + import eventfeed --url=http://www.fsfe.org/events/events.en.rss + +If this command is being used with sudo, make sure to use the -u option so +that the script can operate as the appropriate user. For example: + +sudo -u www-data moin --config-dir=path-to-wiki --wiki-url=example.com/ \ + import eventfeed --url=http://www.fsfe.org/events/events.en.rss + +It may also be necessary to set PYTHONPATH directly before the moin program +name and even to explicitly use the path to that program. + Recommended Software -------------------- @@ -178,6 +217,9 @@ (having no space after the "::" token) which previously captured text from subsequent lines, and merely empty definitions which previously would have produced a single empty value for definitions providing lists of values. + * Added a script to import events from RSS feeds. + * Added support for a link entry in event pages, although this does not + replace the link information provided by the RSS and iCalendar summaries. New in EventAggregator 0.5 (Changes since EventAggregator 0.4) -------------------------------------------------------------- diff -r a14949a7654a -r 5929e8a9b8dd actions/EventAggregatorNewEvent.py --- a/actions/EventAggregatorNewEvent.py Sat Feb 27 01:43:30 2010 +0100 +++ b/actions/EventAggregatorNewEvent.py Sat Mar 06 22:51:27 2010 +0100 @@ -275,21 +275,22 @@ if start_date > end_date: start_date, end_date = end_date, start_date + event_details = { + "start" : start_date, "end" : end_date, + "title" : title, "summary" : title, + "description" : description + } + # Copy the template. - page = PageEditor(request, template) + template_page = PageEditor(request, template) - if not page.exists(): + if not template_page.exists(): return 0, _("Event template not available.") - event_page = EventAggregatorSupport.EventPage(page) - # Use any parent page information. - if parent: - full_title = "%s/%s" % (parent.rstrip("/"), title) - else: - full_title = title + full_title = EventAggregatorSupport.getFullPageName(parent, title) # Load the new page and replace the event details in the body. @@ -298,19 +299,10 @@ if new_page.exists(): return 0, _("The specified page already exists. Please choose another name.") - new_event_page = EventAggregatorSupport.EventPage(new_page) - new_event_page.copyPage(event_page) + # Complete the new page. - if new_event_page.getFormat() == "wiki": - event_details = { - "start" : start_date, "end" : end_date, - "title" : title, "summary" : title, - "description" : description - } - new_event = EventAggregatorSupport.Event(new_event_page, event_details) - new_event_page.setEvents([new_event]) - new_event_page.setCategoryMembership(category_pagenames) - new_event_page.saveChanges() + EventAggregatorSupport.fillEventPageFromTemplate(template_page, + new_page, event_details, category_pagenames) # Redirect and return success. diff -r a14949a7654a -r 5929e8a9b8dd instscripts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/instscripts Sat Mar 06 22:51:27 2010 +0100 @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +from glob import glob +import MoinMoin.script +import os, shutil, sys + +target_dir = os.path.split(MoinMoin.script.__file__)[0] + +test = "--test" in sys.argv or "-n" in sys.argv + +for script in glob(os.path.join("scripts", "*", "*.py")): + subdir = os.path.split(os.path.split(script)[0])[-1] + destination = os.path.join(target_dir, subdir) + print "Copying", script, "to", destination, "...", + if not test: + shutil.copy(script, destination) + print "done." + else: + print "not done (test only)." + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r a14949a7654a -r 5929e8a9b8dd pages/EventTemplate --- a/pages/EventTemplate Sat Feb 27 01:43:30 2010 +0100 +++ b/pages/EventTemplate Sat Mar 06 22:51:27 2010 +0100 @@ -2,6 +2,7 @@ End:: YYYY-MM-DD Topics:: topics Description:: a brief description of the event for the RSS feed + Link:: a link to a Web site for the event ## Summary:: summary/title ## To choose a title or summary different to the page name, or to ## provide a specific form of the page name, uncomment the above entry diff -r a14949a7654a -r 5929e8a9b8dd scripts/import/eventfeed.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/import/eventfeed.py Sat Mar 06 22:51:27 2010 +0100 @@ -0,0 +1,220 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - Event feed importer, based on the FeedReader macro, the irclog + script in MoinMoin, and the EventAggregatorNewEvent action + + @copyright: 2008, 2009, 2010 by Paul Boddie + 2005-2007 MoinMoin:AlexanderSchremmer + 2006 MoinMoin:ThomasWaldmann + + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +from MoinMoin.PageEditor import PageEditor +from MoinMoin.script import MoinScript +import EventAggregatorSupport +import urllib +import xml.dom.pulldom + +# The script's class. + +class PluginScript(MoinScript): + + """\ +Purpose: +======== +This tool imports events from an RSS feed into event pages. + +Detailed Instructions: +====================== +General syntax: moin [options] import eventfeed [eventfeed-options] + +[options] usually should be: + --config-dir=/path/to/my/cfg/ --wiki-url=wiki.example.org/ + +[eventfeed-options] see below: + 0. To import events from the FSFE event feed + moin ... import eventfeed --url=http://www.fsfe.org/events/events.en.rss ... + + 1. To use a specific template such as 'EventTemplate' + moin ... import eventfeed --template=EventTemplate ... + + 2. To assign pages to specific categories such as 'CategoryEvents CategoryMeetings' + moin ... import eventfeed --categories='CategoryEvents CategoryMeetings' ... + + 3. To use a specific author such as 'EventImporter' + moin ... import eventfeed --author=EventImporter ... + + 4. To add pages under a common parent page such as 'Events' + moin ... import eventfeed --parent=Events ... + + 5. To overwrite existing event pages + moin ... import eventfeed --overwrite ... + + 5. To delete any event pages associated with the feed + moin ... import eventfeed --delete ... +""" + + FIELDS = ("title", "link", "description") + + def __init__(self, argv, def_values): + MoinScript.__init__(self, argv, def_values) + self.parser.add_option( + "--url", dest="url", default="", + help="Specify the location of the events RSS feed" + ) + self.parser.add_option( + "--template", dest="template", default="EventTemplate", + help="Specify the template used to make the event pages" + ) + self.parser.add_option( + "--categories", dest="categories", default="CategoryEvents", + help="Specify the categories to which the event pages will belong" + ) + self.parser.add_option( + "--author", dest="author", default="EventImporter", + help="Specify the author of the event pages" + ) + self.parser.add_option( + "--parent", dest="parent", default="", + help="Specify the parent page of the event pages" + ) + self.parser.add_option( + "--overwrite", dest="overwrite", action="store_true", + help="Request that existing pages be overwritten" + ) + self.parser.add_option( + "--delete", dest="delete", action="store_true", + help="Request that event pages associated with the feed be deleted" + ) + + def mainloop(self): + self.init_request() + if not self.options.url: + print "No URL specified. Not importing any events!" + else: + self.read_events(self.options.url) + + def read_events(self, url): + + """ + Read events from the given events RSS feed, specified by 'url', creating + new Wiki pages where appropriate. + """ + + request = self.request + category_pagenames = self.options.categories.split() + + # Locate the template for events. + + template_page = PageEditor(request, self.options.template) + + if not template_page.exists(): + print "Template %r cannot be found. Not importing any events!" % self.options.template + return + + # Process the feed. + + feed = urllib.urlopen(url) + + try: + nodes = xml.dom.pulldom.parse(feed) + event_details = {} + + in_item = 0 + + # Read the nodes from the feed. + + for node_type, value in nodes: + if node_type == xml.dom.pulldom.START_ELEMENT: + if value.nodeName == "item": + in_item = 1 + + # Get the value of the important fields. + + elif in_item and value.nodeName in self.FIELDS: + nodes.expandNode(value) + event_details[value.nodeName] = self.text(value) + + # Where all fields have been read, make a new page. + + if reduce(lambda x, y: x and event_details.has_key(y), self.FIELDS, 1): + + # Define the page. + + title = event_details["title"] + + # Use any parent page information. + + full_title = EventAggregatorSupport.getFullPageName(self.options.parent, title) + + # Find the start and end dates. + + dates = EventAggregatorSupport.getDateStrings(title) + + # Require one or two dates. + + if dates and 1 <= len(dates) <= 2: + + # Deduce the end date. + + if len(dates) == 2: + start_date, end_date = dates + elif len(dates) == 1: + start_date = end_date = dates[0] + + # Load the new page and replace the event details in the body. + + new_page = PageEditor(request, full_title, + uid_override=self.options.author) + + # Delete the page if requested. + + if new_page.exists() and self.options.delete: + + try: + new_page.deletePage() + except new_page.AccessDenied: + print "Page %r has not been deleted." % full_title + + # Complete the new page. + + elif not new_page.exists() or self.options.overwrite: + event_details["summary"] = title + event_details["start"] = start_date + event_details["end"] = end_date + + try: + EventAggregatorSupport.fillEventPageFromTemplate( + template_page, new_page, event_details, + category_pagenames) + + except new_page.Unchanged: + print "Page %r is not changed." % full_title + + else: + print "Not overwriting page %r." % full_title + + else: + print "Could not deduce dates from %r." % title + + event_details = {} + + elif node_type == xml.dom.pulldom.END_ELEMENT: + if value.nodeName == "item": + in_item = 0 + + finally: + feed.close() + + def text(self, element): + + "Return the text within the given 'element'." + + nodes = [] + for node in element.childNodes: + if node.nodeType == node.TEXT_NODE: + nodes.append(node.nodeValue) + return "".join(nodes) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r a14949a7654a -r 5929e8a9b8dd setup.py --- a/setup.py Sat Feb 27 01:43:30 2010 +0100 +++ b/setup.py Sat Mar 06 22:51:27 2010 +0100 @@ -8,6 +8,6 @@ author = "Paul Boddie", author_email = "paul@boddie.org.uk", url = "http://moinmo.in/MacroMarket/EventAggregator", - version = "0.5", + version = "0.6", py_modules = ["EventAggregatorSupport"] )