1.1 --- a/EventAggregatorSupport.py Sat Feb 27 01:43:30 2010 +0100
1.2 +++ b/EventAggregatorSupport.py Sat Mar 06 22:51:27 2010 +0100
1.3 @@ -21,7 +21,7 @@
1.4 except NameError:
1.5 from sets import Set as set
1.6
1.7 -__version__ = "0.5"
1.8 +__version__ = "0.6"
1.9
1.10 # Date labels.
1.11
1.12 @@ -399,7 +399,7 @@
1.13
1.14 # Text which need not be quoted, but it will be Wiki text.
1.15
1.16 - elif term in ("description",):
1.17 + elif term in ("description", "link"):
1.18 desc = event_details[term]
1.19
1.20 replaced_terms.add(term)
1.21 @@ -879,7 +879,7 @@
1.22
1.23 def getDate(s):
1.24
1.25 - "Parse the string 's', extracting and returning a date string."
1.26 + "Parse the string 's', extracting and returning a date object."
1.27
1.28 m = date_regexp.search(s)
1.29 if m:
1.30 @@ -887,9 +887,21 @@
1.31 else:
1.32 return None
1.33
1.34 +def getDateStrings(s):
1.35 +
1.36 + "Parse the string 's', extracting and returning all date strings."
1.37 +
1.38 + start = 0
1.39 + m = date_regexp.search(s, start)
1.40 + l = []
1.41 + while m:
1.42 + l.append("-".join(m.groups()))
1.43 + m = date_regexp.search(s, m.end())
1.44 + return l
1.45 +
1.46 def getMonth(s):
1.47
1.48 - "Parse the string 's', extracting and returning a month string."
1.49 + "Parse the string 's', extracting and returning a month object."
1.50
1.51 m = month_regexp.search(s)
1.52 if m:
1.53 @@ -1013,4 +1025,35 @@
1.54 else:
1.55 return page.link_to_raw(request, text, query_string)
1.56
1.57 +def getFullPageName(parent, title):
1.58 +
1.59 + """
1.60 + Return a full page name from the given 'parent' page (can be empty or None)
1.61 + and 'title' (a simple page name).
1.62 + """
1.63 +
1.64 + if parent:
1.65 + return "%s/%s" % (parent.rstrip("/"), title)
1.66 + else:
1.67 + return title
1.68 +
1.69 +def fillEventPageFromTemplate(template_page, new_page, event_details, category_pagenames):
1.70 +
1.71 + """
1.72 + Using the given 'template_page', complete the 'new_page' by copying the
1.73 + template and adding the given 'event_details' (a dictionary of event
1.74 + fields), setting also the 'category_pagenames' to define category
1.75 + membership.
1.76 + """
1.77 +
1.78 + event_page = EventPage(template_page)
1.79 + new_event_page = EventPage(new_page)
1.80 + new_event_page.copyPage(event_page)
1.81 +
1.82 + if new_event_page.getFormat() == "wiki":
1.83 + new_event = Event(new_event_page, event_details)
1.84 + new_event_page.setEvents([new_event])
1.85 + new_event_page.setCategoryMembership(category_pagenames)
1.86 + new_event_page.saveChanges()
1.87 +
1.88 # vim: tabstop=4 expandtab shiftwidth=4
2.1 --- a/PKG-INFO Sat Feb 27 01:43:30 2010 +0100
2.2 +++ b/PKG-INFO Sat Mar 06 22:51:27 2010 +0100
2.3 @@ -1,12 +1,12 @@
2.4 Metadata-Version: 1.1
2.5 Name: EventAggregator
2.6 -Version: 0.5
2.7 +Version: 0.6
2.8 Author: Paul Boddie
2.9 Author-email: paul at boddie org uk
2.10 Maintainer: Paul Boddie
2.11 Maintainer-email: paul at boddie org uk
2.12 Home-page: http://moinmo.in/MacroMarket/EventAggregator
2.13 -Download-url: http://moinmo.in/MacroMarket/EventAggregator?action=AttachFile&do=view&target=EventAggregator-0.5.tar.gz
2.14 +Download-url: http://moinmo.in/MacroMarket/EventAggregator?action=AttachFile&do=view&target=EventAggregator-0.6.tar.gz
2.15 Summary: Aggregate event data and display it in an event calendar (or summarise it in iCalendar and RSS resources)
2.16 License: GPL (version 2 or later)
2.17 Description: The EventAggregator macro for MoinMoin can be used to display event
3.1 --- a/README.txt Sat Feb 27 01:43:30 2010 +0100
3.2 +++ b/README.txt Sat Mar 06 22:51:27 2010 +0100
3.3 @@ -20,6 +20,9 @@
3.4 categories to which the page will be assigned, and the start and end dates of
3.5 the event.
3.6
3.7 +The eventfeed script can be used to import events from RSS feeds, inserting
3.8 +new pages into a Wiki.
3.9 +
3.10 Important Notices
3.11 -----------------
3.12
3.13 @@ -27,8 +30,8 @@
3.14 links have been fixed to return only events within the specified period and to
3.15 work with day- and month-relative calendars. Users who have bookmarks in their
3.16 Web browser or feed reader should replace these bookmarks by visiting the
3.17 -bookmarked page and acquiring new versions of these links, once EventAggregator
3.18 -has been upgraded.
3.19 +bookmarked page and acquiring new versions of these links, once
3.20 +EventAggregator has been upgraded.
3.21
3.22 Installation
3.23 ------------
3.24 @@ -77,12 +80,24 @@
3.25 @import "event-aggregator-print.css";
3.26 @import "event-aggregator.css";
3.27
3.28 -To install the actions in a Wiki, consider using the instactions script provided:
3.29 +To install the actions in a Wiki, consider using the instactions script
3.30 +provided:
3.31
3.32 ./instactions path-to-wiki
3.33
3.34 On non-UNIX platforms, it is necessary to manually copy the contents of the
3.35 -actions directory in this distribution into the actions directory of your Wiki.
3.36 +actions directory in this distribution into the actions directory of your
3.37 +Wiki.
3.38 +
3.39 +To install the eventfeed script which can import events from RSS feeds, use
3.40 +the instscripts script, making sure to set your PYTHONPATH so that the script
3.41 +can find your MoinMoin installation:
3.42 +
3.43 + PYTHONPATH=path-to-site-packages ./instscripts
3.44 +
3.45 +This script should work on UNIX and non-UNIX systems, although the above
3.46 +example demonstrates setting the PYTHONPATH in the bash shell on UNIX-like
3.47 +systems.
3.48
3.49 Useful Pages
3.50 ------------
3.51 @@ -132,6 +147,30 @@
3.52
3.53 See pages/HelpOnEventAggregatorNewEvent for more detailed information.
3.54
3.55 +Running the Scripts
3.56 +-------------------
3.57 +
3.58 +To import events from an RSS feed, the eventfeed script integrated with the
3.59 +moin program can be used as follows:
3.60 +
3.61 +moin --config-dir=path-to-wiki --wiki-url=example.com/ \
3.62 + import eventfeed --url=url-of-events-feed
3.63 +
3.64 +Thus, to import events from the FSFE events RSS feed, the following command
3.65 +could be used:
3.66 +
3.67 +moin --config-dir=path-to-wiki --wiki-url=example.com/ \
3.68 + import eventfeed --url=http://www.fsfe.org/events/events.en.rss
3.69 +
3.70 +If this command is being used with sudo, make sure to use the -u option so
3.71 +that the script can operate as the appropriate user. For example:
3.72 +
3.73 +sudo -u www-data moin --config-dir=path-to-wiki --wiki-url=example.com/ \
3.74 + import eventfeed --url=http://www.fsfe.org/events/events.en.rss
3.75 +
3.76 +It may also be necessary to set PYTHONPATH directly before the moin program
3.77 +name and even to explicitly use the path to that program.
3.78 +
3.79 Recommended Software
3.80 --------------------
3.81
3.82 @@ -178,6 +217,9 @@
3.83 (having no space after the "::" token) which previously captured text from
3.84 subsequent lines, and merely empty definitions which previously would have
3.85 produced a single empty value for definitions providing lists of values.
3.86 + * Added a script to import events from RSS feeds.
3.87 + * Added support for a link entry in event pages, although this does not
3.88 + replace the link information provided by the RSS and iCalendar summaries.
3.89
3.90 New in EventAggregator 0.5 (Changes since EventAggregator 0.4)
3.91 --------------------------------------------------------------
4.1 --- a/actions/EventAggregatorNewEvent.py Sat Feb 27 01:43:30 2010 +0100
4.2 +++ b/actions/EventAggregatorNewEvent.py Sat Mar 06 22:51:27 2010 +0100
4.3 @@ -275,21 +275,22 @@
4.4 if start_date > end_date:
4.5 start_date, end_date = end_date, start_date
4.6
4.7 + event_details = {
4.8 + "start" : start_date, "end" : end_date,
4.9 + "title" : title, "summary" : title,
4.10 + "description" : description
4.11 + }
4.12 +
4.13 # Copy the template.
4.14
4.15 - page = PageEditor(request, template)
4.16 + template_page = PageEditor(request, template)
4.17
4.18 - if not page.exists():
4.19 + if not template_page.exists():
4.20 return 0, _("Event template not available.")
4.21
4.22 - event_page = EventAggregatorSupport.EventPage(page)
4.23 -
4.24 # Use any parent page information.
4.25
4.26 - if parent:
4.27 - full_title = "%s/%s" % (parent.rstrip("/"), title)
4.28 - else:
4.29 - full_title = title
4.30 + full_title = EventAggregatorSupport.getFullPageName(parent, title)
4.31
4.32 # Load the new page and replace the event details in the body.
4.33
4.34 @@ -298,19 +299,10 @@
4.35 if new_page.exists():
4.36 return 0, _("The specified page already exists. Please choose another name.")
4.37
4.38 - new_event_page = EventAggregatorSupport.EventPage(new_page)
4.39 - new_event_page.copyPage(event_page)
4.40 + # Complete the new page.
4.41
4.42 - if new_event_page.getFormat() == "wiki":
4.43 - event_details = {
4.44 - "start" : start_date, "end" : end_date,
4.45 - "title" : title, "summary" : title,
4.46 - "description" : description
4.47 - }
4.48 - new_event = EventAggregatorSupport.Event(new_event_page, event_details)
4.49 - new_event_page.setEvents([new_event])
4.50 - new_event_page.setCategoryMembership(category_pagenames)
4.51 - new_event_page.saveChanges()
4.52 + EventAggregatorSupport.fillEventPageFromTemplate(template_page,
4.53 + new_page, event_details, category_pagenames)
4.54
4.55 # Redirect and return success.
4.56
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/instscripts Sat Mar 06 22:51:27 2010 +0100
5.3 @@ -0,0 +1,21 @@
5.4 +#!/usr/bin/env python
5.5 +
5.6 +from glob import glob
5.7 +import MoinMoin.script
5.8 +import os, shutil, sys
5.9 +
5.10 +target_dir = os.path.split(MoinMoin.script.__file__)[0]
5.11 +
5.12 +test = "--test" in sys.argv or "-n" in sys.argv
5.13 +
5.14 +for script in glob(os.path.join("scripts", "*", "*.py")):
5.15 + subdir = os.path.split(os.path.split(script)[0])[-1]
5.16 + destination = os.path.join(target_dir, subdir)
5.17 + print "Copying", script, "to", destination, "...",
5.18 + if not test:
5.19 + shutil.copy(script, destination)
5.20 + print "done."
5.21 + else:
5.22 + print "not done (test only)."
5.23 +
5.24 +# vim: tabstop=4 expandtab shiftwidth=4
6.1 --- a/pages/EventTemplate Sat Feb 27 01:43:30 2010 +0100
6.2 +++ b/pages/EventTemplate Sat Mar 06 22:51:27 2010 +0100
6.3 @@ -2,6 +2,7 @@
6.4 End:: YYYY-MM-DD
6.5 Topics:: topics
6.6 Description:: a brief description of the event for the RSS feed
6.7 + Link:: a link to a Web site for the event
6.8 ## Summary:: summary/title
6.9 ## To choose a title or summary different to the page name, or to
6.10 ## provide a specific form of the page name, uncomment the above entry
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
7.2 +++ b/scripts/import/eventfeed.py Sat Mar 06 22:51:27 2010 +0100
7.3 @@ -0,0 +1,220 @@
7.4 +# -*- coding: iso-8859-1 -*-
7.5 +"""
7.6 + MoinMoin - Event feed importer, based on the FeedReader macro, the irclog
7.7 + script in MoinMoin, and the EventAggregatorNewEvent action
7.8 +
7.9 + @copyright: 2008, 2009, 2010 by Paul Boddie <paul@boddie.org.uk>
7.10 + 2005-2007 MoinMoin:AlexanderSchremmer
7.11 + 2006 MoinMoin:ThomasWaldmann
7.12 +
7.13 + @license: GNU GPL (v2 or later), see COPYING.txt for details.
7.14 +"""
7.15 +
7.16 +from MoinMoin.PageEditor import PageEditor
7.17 +from MoinMoin.script import MoinScript
7.18 +import EventAggregatorSupport
7.19 +import urllib
7.20 +import xml.dom.pulldom
7.21 +
7.22 +# The script's class.
7.23 +
7.24 +class PluginScript(MoinScript):
7.25 +
7.26 + """\
7.27 +Purpose:
7.28 +========
7.29 +This tool imports events from an RSS feed into event pages.
7.30 +
7.31 +Detailed Instructions:
7.32 +======================
7.33 +General syntax: moin [options] import eventfeed [eventfeed-options]
7.34 +
7.35 +[options] usually should be:
7.36 + --config-dir=/path/to/my/cfg/ --wiki-url=wiki.example.org/
7.37 +
7.38 +[eventfeed-options] see below:
7.39 + 0. To import events from the FSFE event feed
7.40 + moin ... import eventfeed --url=http://www.fsfe.org/events/events.en.rss ...
7.41 +
7.42 + 1. To use a specific template such as 'EventTemplate'
7.43 + moin ... import eventfeed --template=EventTemplate ...
7.44 +
7.45 + 2. To assign pages to specific categories such as 'CategoryEvents CategoryMeetings'
7.46 + moin ... import eventfeed --categories='CategoryEvents CategoryMeetings' ...
7.47 +
7.48 + 3. To use a specific author such as 'EventImporter'
7.49 + moin ... import eventfeed --author=EventImporter ...
7.50 +
7.51 + 4. To add pages under a common parent page such as 'Events'
7.52 + moin ... import eventfeed --parent=Events ...
7.53 +
7.54 + 5. To overwrite existing event pages
7.55 + moin ... import eventfeed --overwrite ...
7.56 +
7.57 + 5. To delete any event pages associated with the feed
7.58 + moin ... import eventfeed --delete ...
7.59 +"""
7.60 +
7.61 + FIELDS = ("title", "link", "description")
7.62 +
7.63 + def __init__(self, argv, def_values):
7.64 + MoinScript.__init__(self, argv, def_values)
7.65 + self.parser.add_option(
7.66 + "--url", dest="url", default="",
7.67 + help="Specify the location of the events RSS feed"
7.68 + )
7.69 + self.parser.add_option(
7.70 + "--template", dest="template", default="EventTemplate",
7.71 + help="Specify the template used to make the event pages"
7.72 + )
7.73 + self.parser.add_option(
7.74 + "--categories", dest="categories", default="CategoryEvents",
7.75 + help="Specify the categories to which the event pages will belong"
7.76 + )
7.77 + self.parser.add_option(
7.78 + "--author", dest="author", default="EventImporter",
7.79 + help="Specify the author of the event pages"
7.80 + )
7.81 + self.parser.add_option(
7.82 + "--parent", dest="parent", default="",
7.83 + help="Specify the parent page of the event pages"
7.84 + )
7.85 + self.parser.add_option(
7.86 + "--overwrite", dest="overwrite", action="store_true",
7.87 + help="Request that existing pages be overwritten"
7.88 + )
7.89 + self.parser.add_option(
7.90 + "--delete", dest="delete", action="store_true",
7.91 + help="Request that event pages associated with the feed be deleted"
7.92 + )
7.93 +
7.94 + def mainloop(self):
7.95 + self.init_request()
7.96 + if not self.options.url:
7.97 + print "No URL specified. Not importing any events!"
7.98 + else:
7.99 + self.read_events(self.options.url)
7.100 +
7.101 + def read_events(self, url):
7.102 +
7.103 + """
7.104 + Read events from the given events RSS feed, specified by 'url', creating
7.105 + new Wiki pages where appropriate.
7.106 + """
7.107 +
7.108 + request = self.request
7.109 + category_pagenames = self.options.categories.split()
7.110 +
7.111 + # Locate the template for events.
7.112 +
7.113 + template_page = PageEditor(request, self.options.template)
7.114 +
7.115 + if not template_page.exists():
7.116 + print "Template %r cannot be found. Not importing any events!" % self.options.template
7.117 + return
7.118 +
7.119 + # Process the feed.
7.120 +
7.121 + feed = urllib.urlopen(url)
7.122 +
7.123 + try:
7.124 + nodes = xml.dom.pulldom.parse(feed)
7.125 + event_details = {}
7.126 +
7.127 + in_item = 0
7.128 +
7.129 + # Read the nodes from the feed.
7.130 +
7.131 + for node_type, value in nodes:
7.132 + if node_type == xml.dom.pulldom.START_ELEMENT:
7.133 + if value.nodeName == "item":
7.134 + in_item = 1
7.135 +
7.136 + # Get the value of the important fields.
7.137 +
7.138 + elif in_item and value.nodeName in self.FIELDS:
7.139 + nodes.expandNode(value)
7.140 + event_details[value.nodeName] = self.text(value)
7.141 +
7.142 + # Where all fields have been read, make a new page.
7.143 +
7.144 + if reduce(lambda x, y: x and event_details.has_key(y), self.FIELDS, 1):
7.145 +
7.146 + # Define the page.
7.147 +
7.148 + title = event_details["title"]
7.149 +
7.150 + # Use any parent page information.
7.151 +
7.152 + full_title = EventAggregatorSupport.getFullPageName(self.options.parent, title)
7.153 +
7.154 + # Find the start and end dates.
7.155 +
7.156 + dates = EventAggregatorSupport.getDateStrings(title)
7.157 +
7.158 + # Require one or two dates.
7.159 +
7.160 + if dates and 1 <= len(dates) <= 2:
7.161 +
7.162 + # Deduce the end date.
7.163 +
7.164 + if len(dates) == 2:
7.165 + start_date, end_date = dates
7.166 + elif len(dates) == 1:
7.167 + start_date = end_date = dates[0]
7.168 +
7.169 + # Load the new page and replace the event details in the body.
7.170 +
7.171 + new_page = PageEditor(request, full_title,
7.172 + uid_override=self.options.author)
7.173 +
7.174 + # Delete the page if requested.
7.175 +
7.176 + if new_page.exists() and self.options.delete:
7.177 +
7.178 + try:
7.179 + new_page.deletePage()
7.180 + except new_page.AccessDenied:
7.181 + print "Page %r has not been deleted." % full_title
7.182 +
7.183 + # Complete the new page.
7.184 +
7.185 + elif not new_page.exists() or self.options.overwrite:
7.186 + event_details["summary"] = title
7.187 + event_details["start"] = start_date
7.188 + event_details["end"] = end_date
7.189 +
7.190 + try:
7.191 + EventAggregatorSupport.fillEventPageFromTemplate(
7.192 + template_page, new_page, event_details,
7.193 + category_pagenames)
7.194 +
7.195 + except new_page.Unchanged:
7.196 + print "Page %r is not changed." % full_title
7.197 +
7.198 + else:
7.199 + print "Not overwriting page %r." % full_title
7.200 +
7.201 + else:
7.202 + print "Could not deduce dates from %r." % title
7.203 +
7.204 + event_details = {}
7.205 +
7.206 + elif node_type == xml.dom.pulldom.END_ELEMENT:
7.207 + if value.nodeName == "item":
7.208 + in_item = 0
7.209 +
7.210 + finally:
7.211 + feed.close()
7.212 +
7.213 + def text(self, element):
7.214 +
7.215 + "Return the text within the given 'element'."
7.216 +
7.217 + nodes = []
7.218 + for node in element.childNodes:
7.219 + if node.nodeType == node.TEXT_NODE:
7.220 + nodes.append(node.nodeValue)
7.221 + return "".join(nodes)
7.222 +
7.223 +# vim: tabstop=4 expandtab shiftwidth=4
8.1 --- a/setup.py Sat Feb 27 01:43:30 2010 +0100
8.2 +++ b/setup.py Sat Mar 06 22:51:27 2010 +0100
8.3 @@ -8,6 +8,6 @@
8.4 author = "Paul Boddie",
8.5 author_email = "paul@boddie.org.uk",
8.6 url = "http://moinmo.in/MacroMarket/EventAggregator",
8.7 - version = "0.5",
8.8 + version = "0.6",
8.9 py_modules = ["EventAggregatorSupport"]
8.10 )