# HG changeset patch # User Paul Boddie # Date 1367358572 -7200 # Node ID 693940d48e8b0dae3396b81c6752ab4ac9e38b80 # Parent cfb2743a9f38e140d254f79e6d759eb1968b2c69 Split the EventAggregatorSupport code into separate modules within a common package. diff -r cfb2743a9f38 -r 693940d48e8b EventAggregatorSupport/Actions.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/EventAggregatorSupport/Actions.py Tue Apr 30 23:49:32 2013 +0200 @@ -0,0 +1,76 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - EventAggregator action support library + + @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +from DateSupport import * +from MoinSupport import * + +from MoinMoin.wikiutil import escape + +# Utility classes and associated functions. + +class ActionSupport(ActionSupport): + + "Extend the generic action support." + + def get_month_lists(self, default_as_current=0): + + """ + Return two lists of HTML element definitions corresponding to the start + and end month selection controls, with months selected according to any + values that have been specified via request parameters. + """ + + _ = self._ + form = self.get_form() + + # Initialise month lists. + + start_month_list = [] + end_month_list = [] + + start_month = self._get_input(form, "start-month", default_as_current and getCurrentMonth().month() or None) + end_month = self._get_input(form, "end-month", start_month) + + # Prepare month lists, selecting specified months. + + if not default_as_current: + start_month_list.append('') + end_month_list.append('') + + for month in range(1, 13): + month_label = escape(_(getMonthLabel(month))) + selected = self._get_selected(month, start_month) + start_month_list.append('' % (month, selected, month_label)) + selected = self._get_selected(month, end_month) + end_month_list.append('' % (month, selected, month_label)) + + return start_month_list, end_month_list + + def get_year_defaults(self, default_as_current=0): + + "Return defaults for the start and end years." + + form = self.get_form() + + start_year_default = form.get("start-year", [default_as_current and getCurrentYear() or ""])[0] + end_year_default = form.get("end-year", [default_as_current and start_year_default or ""])[0] + + return start_year_default, end_year_default + + def get_day_defaults(self, default_as_current=0): + + "Return defaults for the start and end days." + + form = self.get_form() + + start_day_default = form.get("start-day", [default_as_current and getCurrentDate().day() or ""])[0] + end_day_default = form.get("end-day", [default_as_current and start_day_default or ""])[0] + + return start_day_default, end_day_default + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r cfb2743a9f38 -r 693940d48e8b EventAggregatorSupport/Filter.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/EventAggregatorSupport/Filter.py Tue Apr 30 23:49:32 2013 +0200 @@ -0,0 +1,226 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - EventAggregator event filtering functionality. + + @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +from DateSupport import DateTime, Timespan, TimespanCollection, \ + getCurrentDate, getCurrentMonth +from MoinSupport import * + +# Event filtering and limits. + +def getEventsInPeriod(events, calendar_period): + + """ + Return a collection containing those of the given 'events' which occur + within the given 'calendar_period'. + """ + + all_shown_events = [] + + for event in events: + + # Test for the suitability of the event. + + if event.as_timespan() is not None: + + # Compare the dates to the requested calendar window, if any. + + if event in calendar_period: + all_shown_events.append(event) + + return all_shown_events + +def getEventLimits(events): + + "Return the earliest and latest of the given 'events'." + + earliest = None + latest = None + + for event in events: + + # Test for the suitability of the event. + + if event.as_timespan() is not None: + ts = event.as_timespan() + if earliest is None or ts.start < earliest: + earliest = ts.start + if latest is None or ts.end > latest: + latest = ts.end + + return earliest, latest + +def getLatestEventTimestamp(events): + + """ + Return the latest timestamp found from the given 'events'. + """ + + latest = None + + for event in events: + metadata = event.getMetadata() + + if latest is None or latest < metadata["last-modified"]: + latest = metadata["last-modified"] + + return latest + +def getCalendarPeriod(calendar_start, calendar_end): + + """ + Return a calendar period for the given 'calendar_start' and 'calendar_end'. + These parameters can be given as None. + """ + + # Re-order the window, if appropriate. + + if calendar_start is not None and calendar_end is not None and calendar_start > calendar_end: + calendar_start, calendar_end = calendar_end, calendar_start + + return Timespan(calendar_start, calendar_end) + +def getConcretePeriod(calendar_start, calendar_end, earliest, latest, resolution): + + """ + From the requested 'calendar_start' and 'calendar_end', which may be None, + indicating that no restriction is imposed on the period for each of the + boundaries, use the 'earliest' and 'latest' event months to define a + specific period of interest. + """ + + # Define the period as starting with any specified start month or the + # earliest event known, ending with any specified end month or the latest + # event known. + + first = calendar_start or earliest + last = calendar_end or latest + + # If there is no range of months to show, perhaps because there are no + # events in the requested period, and there was no start or end month + # specified, show only the month indicated by the start or end of the + # requested period. If all events were to be shown but none were found show + # the current month. + + if resolution == "date": + get_current = getCurrentDate + else: + get_current = getCurrentMonth + + if first is None: + first = last or get_current() + if last is None: + last = first or get_current() + + if resolution == "month": + first = first.as_month() + last = last.as_month() + + # Permit "expiring" periods (where the start date approaches the end date). + + return min(first, last), last + +def getCoverage(events, resolution="date"): + + """ + Determine the coverage of the given 'events', returning a collection of + timespans, along with a dictionary mapping locations to collections of + slots, where each slot contains a tuple of the form (timespans, events). + """ + + all_events = {} + full_coverage = TimespanCollection(resolution) + + # Get event details. + + for event in events: + event_details = event.getDetails() + + # Find the coverage of this period for the event. + + # For day views, each location has its own slot, but for month + # views, all locations are pooled together since having separate + # slots for each location can lead to poor usage of vertical space. + + if resolution == "datetime": + event_location = event_details.get("location") + else: + event_location = None + + # Update the overall coverage. + + full_coverage.insert_in_order(event) + + # Add a new events list for a new location. + # Locations can be unspecified, thus None refers to all unlocalised + # events. + + if not all_events.has_key(event_location): + all_events[event_location] = [TimespanCollection(resolution, [event])] + + # Try and fit the event into an events list. + + else: + slot = all_events[event_location] + + for slot_events in slot: + + # Where the event does not overlap with the events in the + # current collection, add it alongside these events. + + if not event in slot_events: + slot_events.insert_in_order(event) + break + + # Make a new element in the list if the event cannot be + # marked alongside existing events. + + else: + slot.append(TimespanCollection(resolution, [event])) + + return full_coverage, all_events + +def getCoverageScale(coverage): + + """ + Return a scale for the given coverage so that the times involved are + exposed. The scale consists of a list of non-overlapping timespans forming + a contiguous period of time. + """ + + times = set() + for timespan in coverage: + start, end = timespan.as_limits() + + # Add either genuine times or dates converted to times. + + if isinstance(start, DateTime): + times.add(start) + else: + times.add(start.as_start_of_day()) + + if isinstance(end, DateTime): + times.add(end) + else: + times.add(end.as_date().next_day()) + + times = list(times) + times.sort(cmp_dates_as_day_start) + + scale = [] + first = 1 + start = None + for time in times: + if not first: + scale.append(Timespan(start, time)) + else: + first = 0 + start = time + + return scale + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r cfb2743a9f38 -r 693940d48e8b EventAggregatorSupport/Formatting.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/EventAggregatorSupport/Formatting.py Tue Apr 30 23:49:32 2013 +0200 @@ -0,0 +1,292 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - EventAggregator event formatting + + @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie + @copyright: 2000-2004 Juergen Hermann , + 2005-2008 MoinMoin:ThomasWaldmann. + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +from MoinSupport import * +from MoinMoin.wikiutil import escape + +try: + import vCalendar +except ImportError: + vCalendar = None + +# Event-only formatting. + +def formatEvent(event, request, fmt, write=None): + + """ + Format the given 'event' using the 'request' and formatter 'fmt'. If the + 'write' parameter is specified, use it to write output. + """ + + details = event.getDetails() + raw_details = event.getRawDetails() + write = write or request.write + + if details.has_key("fragment"): + write(fmt.anchordef(details["fragment"])) + + # Promote any title to a heading above the event details. + + if raw_details.has_key("title"): + write(formatText(raw_details["title"], request, fmt)) + elif details.has_key("title"): + write(fmt.heading(on=1, depth=1)) + write(fmt.text(details["title"])) + write(fmt.heading(on=0, depth=1)) + + # Produce a definition list for the rest of the details. + + write(fmt.definition_list(on=1)) + + for term in event.all_terms: + if term == "title": + continue + + raw_value = raw_details.get(term) + value = details.get(term) + + if raw_value or value: + write(fmt.definition_term(on=1)) + write(fmt.text(term)) + write(fmt.definition_term(on=0)) + write(fmt.definition_desc(on=1)) + + # Try and use the raw details, if available. + + if raw_value: + write(formatText(raw_value, request, fmt)) + + # Otherwise, format the processed details. + + else: + if term in event.list_terms: + write(", ".join([formatText(str(v), request, fmt) for v in value])) + else: + write(fmt.text(str(value))) + + write(fmt.definition_desc(on=0)) + + write(fmt.definition_list(on=0)) + +def formatEventsForOutputType(events, request, mimetype, parent=None, descriptions=None, latest_timestamp=None, write=None): + + """ + Format the given 'events' using the 'request' for the given 'mimetype'. + + The optional 'parent' indicates the "natural" parent page of the events. Any + event pages residing beneath the parent page will have their names + reproduced as relative to the parent page. + + The optional 'descriptions' indicates the nature of any description given + for events in the output resource. + + The optional 'latest_timestamp' indicates the timestamp of the latest edit + of the page or event collection. + + If the 'write' parameter is specified, use it to write output. + """ + + write = write or request.write + + # Start the collection. + + if mimetype == "text/calendar" and vCalendar is not None: + write = vCalendar.iterwrite(write=write).write + write("BEGIN", {}, "VCALENDAR") + write("PRODID", {}, "-//MoinMoin//EventAggregatorSummary") + write("VERSION", {}, "2.0") + + elif mimetype == "application/rss+xml": + + # Using the page name and the page URL in the title, link and + # description. + + path_info = getPathInfo(request) + + write('\r\n') + write('\r\n') + write('%s\r\n' % path_info[1:]) + write('%s%s\r\n' % (request.getBaseURL(), path_info)) + write('Events published on %s%s\r\n' % (request.getBaseURL(), path_info)) + + if latest_timestamp is not None: + write('%s\r\n' % latest_timestamp.as_HTTP_datetime_string()) + + # Sort the events by start date, reversed. + + ordered_events = getOrderedEvents(events) + ordered_events.reverse() + events = ordered_events + + elif mimetype == "text/html": + write('') + write('') + + # Output the collection one by one. + + for event in events: + formatEventForOutputType(event, request, mimetype, parent, descriptions) + + # End the collection. + + if mimetype == "text/calendar" and vCalendar is not None: + write("END", {}, "VCALENDAR") + + elif mimetype == "application/rss+xml": + write('\r\n') + write('\r\n') + + elif mimetype == "text/html": + write('') + write('') + +def formatEventForOutputType(event, request, mimetype, parent=None, descriptions=None, write=None): + + """ + Format the given 'event' using the 'request' for the given 'mimetype'. + + The optional 'parent' indicates the "natural" parent page of the events. Any + event pages residing beneath the parent page will have their names + reproduced as relative to the parent page. + + The optional 'descriptions' indicates the nature of any description given + for events in the output resource. + + If the 'write' parameter is specified, use it to write output. + """ + + write = write or request.write + event_details = event.getDetails() + event_metadata = event.getMetadata() + + if mimetype == "text/calendar" and vCalendar is not None: + + # NOTE: A custom formatter making attributes for links and plain + # NOTE: text for values could be employed here. + + write = vCalendar.iterwrite(write=write).write + + # Get the summary details. + + event_summary = event.getSummary(parent) + link = event.getEventURL() + + # Output the event details. + + write("BEGIN", {}, "VEVENT") + write("UID", {}, link) + write("URL", {}, link) + write("DTSTAMP", {}, "%04d%02d%02dT%02d%02d%02dZ" % event_metadata["created"].as_tuple()[:6]) + write("LAST-MODIFIED", {}, "%04d%02d%02dT%02d%02d%02dZ" % event_metadata["last-modified"].as_tuple()[:6]) + write("SEQUENCE", {}, "%d" % event_metadata["sequence"]) + + start = event_details["start"] + end = event_details["end"] + + if isinstance(start, DateTime): + params, value = getCalendarDateTime(start) + else: + params, value = {"VALUE" : "DATE"}, "%04d%02d%02d" % start.as_date().as_tuple() + write("DTSTART", params, value) + + if isinstance(end, DateTime): + params, value = getCalendarDateTime(end) + else: + params, value = {"VALUE" : "DATE"}, "%04d%02d%02d" % end.next_day().as_date().as_tuple() + write("DTEND", params, value) + + write("SUMMARY", {}, event_summary) + + # Optional details. + + if event_details.get("topics") or event_details.get("categories"): + write("CATEGORIES", {}, event_details.get("topics") or event_details.get("categories")) + if event_details.has_key("location"): + write("LOCATION", {}, event_details["location"]) + if event_details.has_key("geo"): + write("GEO", {}, tuple([str(ref.to_degrees()) for ref in event_details["geo"]])) + + write("END", {}, "VEVENT") + + elif mimetype == "application/rss+xml": + + event_page = event.getPage() + event_details = event.getDetails() + + # Get a parser and formatter for the formatting of some attributes. + + fmt = request.html_formatter + + # Get the summary details. + + event_summary = event.getSummary(parent) + link = event.getEventURL() + + write('\r\n') + write('%s\r\n' % escape(event_summary)) + write('%s\r\n' % link) + + # Write a description according to the preferred source of + # descriptions. + + if descriptions == "page": + description = event_details.get("description", "") + else: + description = event_metadata["last-comment"] + + write('%s\r\n' % + fmt.text(event_page.formatText(description, fmt))) + + for topic in event_details.get("topics") or event_details.get("categories") or []: + write('%s\r\n' % + fmt.text(event_page.formatText(topic, fmt))) + + write('%s\r\n' % event_metadata["created"].as_HTTP_datetime_string()) + write('%s#%s\r\n' % (link, event_metadata["sequence"])) + write('\r\n') + + elif mimetype == "text/html": + fmt = request.html_formatter + fmt.setPage(request.page) + formatEvent(event, request, fmt, write=write) + +# iCalendar format helper functions. + +def getCalendarDateTime(datetime): + + """ + Write to the given 'request' the 'datetime' using appropriate time zone + information. + """ + + utc_datetime = datetime.to_utc() + if utc_datetime: + return {"VALUE" : "DATE-TIME"}, "%04d%02d%02dT%02d%02d%02dZ" % utc_datetime.padded().as_tuple()[:-1] + else: + zone = datetime.time_zone() + params = {"VALUE" : "DATE-TIME"} + if zone: + params["TZID"] = zone + return params, "%04d%02d%02dT%02d%02d%02d" % datetime.padded().as_tuple()[:-1] + +# Helper functions. + +def getOrderedEvents(events): + + """ + Return a list with the given 'events' ordered according to their start and + end dates. + """ + + ordered_events = events[:] + ordered_events.sort() + return ordered_events + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r cfb2743a9f38 -r 693940d48e8b EventAggregatorSupport/Locations.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/EventAggregatorSupport/Locations.py Tue Apr 30 23:49:32 2013 +0200 @@ -0,0 +1,68 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - EventAggregator location handling + + @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +from LocationSupport import getMapReference + +def getMapsPage(request): + return getattr(request.cfg, "event_aggregator_maps_page", "EventMapsDict") + +def getLocationsPage(request): + return getattr(request.cfg, "event_aggregator_locations_page", "EventLocationsDict") + +class Location: + + """ + A representation of a location acquired from the locations dictionary. + + The locations dictionary is a mapping from location to a string containing + white-space-separated values describing... + + * The latitude and longitude of the location. + * Optionally, the time regime used by the location. + """ + + def __init__(self, location, locations): + + """ + Initialise the given 'location' using the 'locations' dictionary + provided. + """ + + self.location = location + + try: + self.data = locations[location].split() + except KeyError: + self.data = [] + + def getPosition(self): + + """ + Attempt to return the position of this location. If no position can be + found, return a latitude of None and a longitude of None. + """ + + try: + latitude, longitude = map(getMapReference, self.data[:2]) + return latitude, longitude + except ValueError: + return None, None + + def getTimeRegime(self): + + """ + Attempt to return the time regime employed at this location. If no + regime has been specified, return None. + """ + + try: + return self.data[2] + except IndexError: + return None + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r cfb2743a9f38 -r 693940d48e8b EventAggregatorSupport/Resources.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/EventAggregatorSupport/Resources.py Tue Apr 30 23:49:32 2013 +0200 @@ -0,0 +1,205 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - EventAggregator resource acquisition and access + + @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +from EventAggregatorSupport.Filter import * +from EventAggregatorSupport.Types import * + +from ContentTypeSupport import getContentTypeAndEncoding +from DateSupport import Date, Month +from MoinSupport import * +from MoinRemoteSupport import getCachedResource + +import codecs +import urllib + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +try: + import vCalendar +except ImportError: + vCalendar = None + +# Obtaining event containers and events from such containers. + +def getEventPages(pages): + + "Return a list of events found on the given 'pages'." + + # Get real pages instead of result pages. + + return map(EventPage, pages) + +def getAllEventSources(request): + + "Return all event sources defined in the Wiki using the 'request'." + + sources_page = getattr(request.cfg, "event_aggregator_sources_page", "EventSourcesDict") + + # Remote sources are accessed via dictionary page definitions. + + return getWikiDict(sources_page, request) + +def getEventResources(sources, calendar_start, calendar_end, request): + + """ + Return resource objects for the given 'sources' using the given + 'calendar_start' and 'calendar_end' to parameterise requests to the sources, + and the 'request' to access configuration settings in the Wiki. + """ + + sources_dict = getAllEventSources(request) + if not sources_dict: + return [] + + # Use dates for the calendar limits. + + if isinstance(calendar_start, Date): + pass + elif isinstance(calendar_start, Month): + calendar_start = calendar_start.as_date(1) + + if isinstance(calendar_end, Date): + pass + elif isinstance(calendar_end, Month): + calendar_end = calendar_end.as_date(-1) + + resources = [] + + for source in sources: + try: + details = sources_dict[source].split() + url = details[0] + format = (details[1:] or ["ical"])[0] + except (KeyError, ValueError): + pass + else: + # Prevent local file access. + + if url.startswith("file:"): + continue + + # Parameterise the URL. + # Where other parameters are used, care must be taken to encode them + # properly. + + url = url.replace("{start}", urllib.quote_plus(calendar_start and str(calendar_start) or "")) + url = url.replace("{end}", urllib.quote_plus(calendar_end and str(calendar_end) or "")) + + # Get a parser. + # NOTE: This could be done reactively by choosing a parser based on + # NOTE: the content type provided by the URL. + + if format == "ical" and vCalendar is not None: + parser = vCalendar.parse + resource_cls = EventCalendar + required_content_type = "text/calendar" + else: + continue + + # Obtain the resource, using a cached version if appropriate. + + max_cache_age = int(getattr(request.cfg, "event_aggregator_max_cache_age", "300")) + data = getCachedResource(request, url, "EventAggregator", "wiki", max_cache_age) + if not data: + continue + + # Process the entry, parsing the content. + + f = StringIO(data) + try: + url = f.readline() + + # Get the content type and encoding, making sure that the data + # can be parsed. + + content_type, encoding = getContentTypeAndEncoding(f.readline()) + if content_type != required_content_type: + continue + + # Send the data to the parser. + + uf = codecs.getreader(encoding or "utf-8")(f) + try: + resources.append(resource_cls(url, parser(uf))) + finally: + uf.close() + finally: + f.close() + + return resources + +def getEventsFromResources(resources): + + "Return a list of events supplied by the given event 'resources'." + + events = [] + + for resource in resources: + + # Get all events described by the resource. + + for event in resource.getEvents(): + + # Remember the event. + + events.append(event) + + return events + +# Page-related functions. + +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.flushEventDetails() + + return new_event_page.getBody() + +# Event selection from request parameters. + +def getEventsUsingParameters(category_names, search_pattern, remote_sources, + calendar_start, calendar_end, resolution, request): + + "Get the events according to the resolution of the calendar." + + if search_pattern: + results = getPagesForSearch(search_pattern, request) + else: + results = [] + + results += getAllCategoryPages(category_names, request) + pages = getPagesFromResults(results, request) + events = getEventsFromResources(getEventPages(pages)) + events += getEventsFromResources(getEventResources(remote_sources, calendar_start, calendar_end, request)) + all_shown_events = getEventsInPeriod(events, getCalendarPeriod(calendar_start, calendar_end)) + earliest, latest = getEventLimits(all_shown_events) + + # Get a concrete period of time. + + first, last = getConcretePeriod(calendar_start, calendar_end, earliest, latest, resolution) + + return all_shown_events, first, last + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r cfb2743a9f38 -r 693940d48e8b EventAggregatorSupport/Types.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/EventAggregatorSupport/Types.py Tue Apr 30 23:49:32 2013 +0200 @@ -0,0 +1,720 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - EventAggregator object types + + @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie + @copyright: 2000-2004 Juergen Hermann , + 2005-2008 MoinMoin:ThomasWaldmann. + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +from GeneralSupport import to_list +from LocationSupport import getMapReference +from MoinSupport import * + +import re + +try: + set +except NameError: + from sets import Set as set + +# Page parsing. + +definition_list_regexp = re.compile(ur'(?P^(?P#*)\s+(?P.*?):: )(?P.*?)$', re.UNICODE | re.MULTILINE) +category_membership_regexp = re.compile(ur"^\s*(?:(Category\S+)(?:\s+(Category\S+))*)\s*$", re.MULTILINE | re.UNICODE) + +# Event parsing from page texts. + +def parseEvents(text, event_page, fragment=None): + + """ + Parse events in the given 'text', returning a list of event objects for the + given 'event_page'. An optional 'fragment' can be specified to indicate a + specific region of the event page. + + If the optional 'fragment' identifier is provided, the first heading may + also be used to provide an event summary/title. + """ + + template_details = {} + if fragment: + template_details["fragment"] = fragment + + details = {} + details.update(template_details) + raw_details = {} + + # Obtain a heading, if requested. + + if fragment: + for level, title, (start, end) in getHeadings(text): + raw_details["title"] = text[start:end] + details["title"] = getSimpleWikiText(title.strip()) + break + + # Start populating events. + + events = [Event(event_page, details, raw_details)] + + for match in definition_list_regexp.finditer(text): + + # Skip commented-out items. + + if match.group("optcomment"): + continue + + # Permit case-insensitive list terms. + + term = match.group("term").lower() + raw_desc = match.group("desc") + + # Special value type handling. + + # Dates. + + if term in Event.date_terms: + desc = getDateTime(raw_desc) + + # Lists (whose elements may be quoted). + + elif term in Event.list_terms: + desc = map(getSimpleWikiText, to_list(raw_desc, ",")) + + # Position details. + + elif term == "geo": + try: + desc = map(getMapReference, to_list(raw_desc, None)) + if len(desc) != 2: + continue + except (KeyError, ValueError): + continue + + # Labels which may well be quoted. + + elif term in Event.title_terms: + desc = getSimpleWikiText(raw_desc.strip()) + + # Plain Wiki text terms. + + elif term in Event.other_terms: + desc = raw_desc.strip() + + else: + desc = raw_desc + + if desc is not None: + + # Handle apparent duplicates by creating a new set of + # details. + + if details.has_key(term): + + # Make a new event. + + details = {} + details.update(template_details) + raw_details = {} + events.append(Event(event_page, details, raw_details)) + + details[term] = desc + raw_details[term] = raw_desc + + return events + +# Event resources providing collections of events. + +class EventResource: + + "A resource providing event information." + + def __init__(self, url): + self.url = url + + def getPageURL(self): + + "Return the URL of this page." + + return self.url + + def getFormat(self): + + "Get the format used by this resource." + + return "plain" + + def getMetadata(self): + + """ + Return a dictionary containing items describing the page's "created" + time, "last-modified" time, "sequence" (or revision number) and the + "last-comment" made about the last edit. + """ + + return {} + + def getEvents(self): + + "Return a list of events from this resource." + + return [] + + def linkToPage(self, request, text, query_string=None, anchor=None): + + """ + Using 'request', return a link to this page with the given link 'text' + and optional 'query_string' and 'anchor'. + """ + + return linkToResource(self.url, request, text, query_string, anchor) + + # Formatting-related functions. + + def formatText(self, text, fmt): + + """ + Format the given 'text' using the specified formatter 'fmt'. + """ + + # Assume plain text which is then formatted appropriately. + + return fmt.text(text) + +class EventCalendar(EventResource): + + "An iCalendar resource." + + def __init__(self, url, calendar): + EventResource.__init__(self, url) + self.calendar = calendar + self.events = None + + def getEvents(self): + + "Return a list of events from this resource." + + if self.events is None: + self.events = [] + + _calendar, _empty, calendar = self.calendar + + for objtype, attrs, obj in calendar: + + # Read events. + + if objtype == "VEVENT": + details = {} + + for property, attrs, value in obj: + + # Convert dates. + + if property in ("DTSTART", "DTEND", "CREATED", "DTSTAMP", "LAST-MODIFIED"): + if property in ("DTSTART", "DTEND"): + property = property[2:] + if attrs.get("VALUE") == "DATE": + value = getDateFromCalendar(value) + if value and property == "END": + value = value.previous_day() + else: + value = getDateTimeFromCalendar(value) + + # Convert numeric data. + + elif property == "SEQUENCE": + value = int(value) + + # Convert lists. + + elif property == "CATEGORIES": + value = to_list(value, ",") + + # Convert positions (using decimal values). + + elif property == "GEO": + try: + value = map(getMapReferenceFromDecimal, to_list(value, ";")) + if len(value) != 2: + continue + except (KeyError, ValueError): + continue + + # Accept other textual data as it is. + + elif property in ("LOCATION", "SUMMARY", "URL"): + value = value or None + + # Ignore other properties. + + else: + continue + + property = property.lower() + details[property] = value + + self.events.append(CalendarEvent(self, details)) + + return self.events + +class EventPage: + + "An event page acting as an event resource." + + def __init__(self, page): + self.page = page + self.events = None + self.body = None + self.categories = None + self.metadata = None + + def copyPage(self, page): + + "Copy the body of the given 'page'." + + self.body = page.getBody() + + def getPageURL(self): + + "Return the URL of this page." + + return getPageURL(self.page) + + def getFormat(self): + + "Get the format used on this page." + + return getFormat(self.page) + + def getMetadata(self): + + """ + Return a dictionary containing items describing the page's "created" + time, "last-modified" time, "sequence" (or revision number) and the + "last-comment" made about the last edit. + """ + + if self.metadata is None: + self.metadata = getMetadata(self.page) + return self.metadata + + def getRevisions(self): + + "Return a list of page revisions." + + return self.page.getRevList() + + def getPageRevision(self): + + "Return the revision details dictionary for this page." + + return getPageRevision(self.page) + + def getPageName(self): + + "Return the page name." + + return self.page.page_name + + def getPrettyPageName(self): + + "Return a nicely formatted title/name for this page." + + return getPrettyPageName(self.page) + + def getBody(self): + + "Get the current page body." + + if self.body is None: + self.body = self.page.get_raw_body() + return self.body + + def getEvents(self): + + "Return a list of events from this page." + + if self.events is None: + self.events = [] + if self.getFormat() == "wiki": + for format, attributes, region in getFragments(self.getBody(), True): + self.events += parseEvents(region, self, attributes.get("fragment")) + + return self.events + + def setEvents(self, events): + + "Set the given 'events' on this page." + + self.events = events + + def getCategoryMembership(self): + + "Get the category names from this page." + + if self.categories is None: + body = self.getBody() + match = category_membership_regexp.search(body) + self.categories = match and [x for x in match.groups() if x] or [] + + return self.categories + + def setCategoryMembership(self, category_names): + + """ + Set the category membership for the page using the specified + 'category_names'. + """ + + self.categories = category_names + + def flushEventDetails(self): + + "Flush the current event details to this page's body text." + + new_body_parts = [] + end_of_last_match = 0 + body = self.getBody() + + events = iter(self.getEvents()) + + event = events.next() + event_details = event.getDetails() + replaced_terms = set() + + for match in definition_list_regexp.finditer(body): + + # Permit case-insensitive list terms. + + term = match.group("term").lower() + desc = match.group("desc") + + # Check that the term has not already been substituted. If so, + # get the next event. + + if term in replaced_terms: + try: + event = events.next() + + # No more events. + + except StopIteration: + break + + event_details = event.getDetails() + replaced_terms = set() + + # Add preceding text to the new body. + + new_body_parts.append(body[end_of_last_match:match.start()]) + + # Get the matching regions, adding the term to the new body. + + new_body_parts.append(match.group("wholeterm")) + + # Special value type handling. + + if event_details.has_key(term): + + # Dates. + + if term in event.date_terms: + desc = desc.replace("YYYY-MM-DD", str(event_details[term])) + + # Lists (whose elements may be quoted). + + elif term in event.list_terms: + desc = ", ".join([getEncodedWikiText(item) for item in event_details[term]]) + + # Labels which must be quoted. + + elif term in event.title_terms: + desc = getEncodedWikiText(event_details[term]) + + # Position details. + + elif term == "geo": + desc = " ".join(map(str, event_details[term])) + + # Text which need not be quoted, but it will be Wiki text. + + elif term in event.other_terms: + desc = event_details[term] + + replaced_terms.add(term) + + # Add the replaced value. + + new_body_parts.append(desc) + + # Remember where in the page has been processed. + + end_of_last_match = match.end() + + # Write the rest of the page. + + new_body_parts.append(body[end_of_last_match:]) + + self.body = "".join(new_body_parts) + + def flushCategoryMembership(self): + + "Flush the category membership to the page body." + + body = self.getBody() + category_names = self.getCategoryMembership() + match = category_membership_regexp.search(body) + + if match: + self.body = "".join([body[:match.start()], " ".join(category_names), body[match.end():]]) + + def saveChanges(self): + + "Save changes to the event." + + self.flushEventDetails() + self.flushCategoryMembership() + self.page.saveText(self.getBody(), 0) + + def linkToPage(self, request, text, query_string=None, anchor=None): + + """ + Using 'request', return a link to this page with the given link 'text' + and optional 'query_string' and 'anchor'. + """ + + return linkToPage(request, self.page, text, query_string, anchor) + + # Formatting-related functions. + + def getParserClass(self, format): + + """ + Return a parser class for the given 'format', returning a plain text + parser if no parser can be found for the specified 'format'. + """ + + return getParserClass(self.page.request, format) + + def formatText(self, text, fmt): + + """ + Format the given 'text' using the specified formatter 'fmt'. + """ + + fmt.page = page = self.page + request = page.request + + parser_cls = self.getParserClass(self.getFormat()) + return formatText(text, request, fmt, parser_cls) + +# Event details. + +class Event(ActsAsTimespan): + + "A description of an event." + + title_terms = "title", "summary" + date_terms = "start", "end" + list_terms = "topics", "categories" + other_terms = "description", "location", "link" + geo_terms = "geo", + all_terms = title_terms + date_terms + list_terms + other_terms + geo_terms + + def __init__(self, page, details, raw_details=None): + self.page = page + self.details = details + self.raw_details = raw_details + + # Permit omission of the end of the event by duplicating the start. + + if self.details.has_key("start") and not self.details.get("end"): + end = self.details["start"] + + # Make any end time refer to the day instead. + + if isinstance(end, DateTime): + end = end.as_date() + + self.details["end"] = end + + def __repr__(self): + return "" % (self.getSummary(), self.as_limits()) + + def __hash__(self): + + """ + Return a dictionary hash, avoiding mistaken equality of events in some + situations (notably membership tests) by including the URL as well as + the summary. + """ + + return hash(self.getSummary() + self.getEventURL()) + + def getPage(self): + + "Return the page describing this event." + + return self.page + + def setPage(self, page): + + "Set the 'page' describing this event." + + self.page = page + + def getEventURL(self): + + "Return the URL of this event." + + fragment = self.details.get("fragment") + return self.page.getPageURL() + (fragment and "#" + fragment or "") + + def linkToEvent(self, request, text, query_string=None): + + """ + Using 'request', return a link to this event with the given link 'text' + and optional 'query_string'. + """ + + return self.page.linkToPage(request, text, query_string, self.details.get("fragment")) + + def getMetadata(self): + + """ + Return a dictionary containing items describing the event's "created" + time, "last-modified" time, "sequence" (or revision number) and the + "last-comment" made about the last edit. + """ + + # Delegate this to the page. + + return self.page.getMetadata() + + def getSummary(self, event_parent=None): + + """ + Return either the given title or summary of the event according to the + event details, or a summary made from using the pretty version of the + page name. + + If the optional 'event_parent' is specified, any page beneath the given + 'event_parent' page in the page hierarchy will omit this parent information + if its name is used as the summary. + """ + + event_details = self.details + + if event_details.has_key("title"): + return event_details["title"] + elif event_details.has_key("summary"): + return event_details["summary"] + else: + # If appropriate, remove the parent details and "/" character. + + title = self.page.getPageName() + + if event_parent and title.startswith(event_parent): + title = title[len(event_parent.rstrip("/")) + 1:] + + return getPrettyTitle(title) + + def getDetails(self): + + "Return the details for this event." + + return self.details + + def setDetails(self, event_details): + + "Set the 'event_details' for this event." + + self.details = event_details + + def getRawDetails(self): + + "Return the details for this event as they were written in a page." + + return self.raw_details + + # Timespan-related methods. + + def __contains__(self, other): + return self == other + + def __eq__(self, other): + if isinstance(other, Event): + return self.getSummary() == other.getSummary() and self.getEventURL() == other.getEventURL() and self._cmp(other) + else: + return self._cmp(other) == 0 + + def __ne__(self, other): + return not self.__eq__(other) + + def __lt__(self, other): + return self._cmp(other) == -1 + + def __le__(self, other): + return self._cmp(other) in (-1, 0) + + def __gt__(self, other): + return self._cmp(other) == 1 + + def __ge__(self, other): + return self._cmp(other) in (0, 1) + + def _cmp(self, other): + + "Compare this event to an 'other' event purely by their timespans." + + if isinstance(other, Event): + return cmp(self.as_timespan(), other.as_timespan()) + else: + return cmp(self.as_timespan(), other) + + def as_timespan(self): + details = self.details + if details.has_key("start") and details.has_key("end"): + return Timespan(details["start"], details["end"]) + else: + return None + + def as_limits(self): + ts = self.as_timespan() + return ts and ts.as_limits() + +class CalendarEvent(Event): + + "An event from a remote calendar." + + def getEventURL(self): + + "Return the URL of this event." + + return self.details.get("url") or self.page.getPageURL() + + def linkToEvent(self, request, text, query_string=None, anchor=None): + + """ + Using 'request', return a link to this event with the given link 'text' + and optional 'query_string' and 'anchor'. + """ + + return linkToResource(self.getEventURL(), request, text, query_string, anchor) + + def getMetadata(self): + + """ + Return a dictionary containing items describing the event's "created" + time, "last-modified" time, "sequence" (or revision number) and the + "last-comment" made about the last edit. + """ + + return { + "created" : self.details.get("created") or self.details["dtstamp"], + "last-modified" : self.details.get("last-modified") or self.details["dtstamp"], + "sequence" : self.details.get("sequence") or 0, + "last-comment" : "" + } + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r cfb2743a9f38 -r 693940d48e8b EventAggregatorSupport/View.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/EventAggregatorSupport/View.py Tue Apr 30 23:49:32 2013 +0200 @@ -0,0 +1,2128 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - EventAggregator user interface library + + @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +from EventAggregatorSupport.Filter import getCalendarPeriod, getEventsInPeriod, \ + getCoverage, getCoverageScale +from EventAggregatorSupport.Locations import getMapsPage, getLocationsPage, Location + +from GeneralSupport import sort_none_first +from LocationSupport import getMapReference, getNormalisedLocation, \ + getPositionForCentrePoint, getPositionForReference +from MoinDateSupport import getFullDateLabel, getFullMonthLabel +from MoinSupport import * +from ViewSupport import getColour, getBlackOrWhite + +from MoinMoin.Page import Page +from MoinMoin.action import AttachFile +from MoinMoin import wikiutil + +try: + set +except NameError: + from sets import Set as set + +# Utility functions. + +def to_plain_text(s, request): + + "Convert 's' to plain text." + + fmt = getFormatterClass(request, "plain")(request) + fmt.setPage(request.page) + return formatText(s, request, fmt) + +def getLocationPosition(location, locations): + + """ + Attempt to return the position of the given 'location' using the 'locations' + dictionary provided. If no position can be found, return a latitude of None + and a longitude of None. + """ + + latitude, longitude = None, None + + if location is not None: + try: + latitude, longitude = map(getMapReference, locations[location].split()) + except (KeyError, ValueError): + pass + + return latitude, longitude + +# Event sorting. + +def sort_start_first(x, y): + x_ts = x.as_limits() + if x_ts is not None: + x_start, x_end = x_ts + y_ts = y.as_limits() + if y_ts is not None: + y_start, y_end = y_ts + start_order = cmp(x_start, y_start) + if start_order == 0: + return cmp(x_end, y_end) + else: + return start_order + return 0 + +# User interface abstractions. + +class View: + + "A view of the event calendar." + + def __init__(self, page, calendar_name, + raw_calendar_start, raw_calendar_end, + original_calendar_start, original_calendar_end, + calendar_start, calendar_end, + wider_calendar_start, wider_calendar_end, + first, last, category_names, remote_sources, search_pattern, template_name, + parent_name, mode, resolution, name_usage, map_name): + + """ + Initialise the view with the current 'page', a 'calendar_name' (which + may be None), the 'raw_calendar_start' and 'raw_calendar_end' (which + are the actual start and end values provided by the request), the + calculated 'original_calendar_start' and 'original_calendar_end' (which + are the result of calculating the calendar's limits from the raw start + and end values), the requested, calculated 'calendar_start' and + 'calendar_end' (which may involve different start and end values due to + navigation in the user interface), and the requested + 'wider_calendar_start' and 'wider_calendar_end' (which indicate a wider + view used when navigating out of the day view), along with the 'first' + and 'last' months of event coverage. + + The additional 'category_names', 'remote_sources', 'search_pattern', + 'template_name', 'parent_name' and 'mode' parameters are used to + configure the links employed by the view. + + The 'resolution' affects the view for certain modes and is also used to + parameterise links. + + The 'name_usage' parameter controls how names are shown on calendar mode + events, such as how often labels are repeated. + + The 'map_name' parameter provides the name of a map to be used in the + map mode. + """ + + self.page = page + self.calendar_name = calendar_name + self.raw_calendar_start = raw_calendar_start + self.raw_calendar_end = raw_calendar_end + self.original_calendar_start = original_calendar_start + self.original_calendar_end = original_calendar_end + self.calendar_start = calendar_start + self.calendar_end = calendar_end + self.wider_calendar_start = wider_calendar_start + self.wider_calendar_end = wider_calendar_end + self.template_name = template_name + self.parent_name = parent_name + self.mode = mode + self.resolution = resolution + self.name_usage = name_usage + self.map_name = map_name + + # Search-related parameters for links. + + self.category_name_parameters = "&".join([("category=%s" % name) for name in category_names]) + self.remote_source_parameters = "&".join([("source=%s" % source) for source in remote_sources]) + self.search_pattern = search_pattern + + # Calculate the duration in terms of the highest common unit of time. + + self.first = first + self.last = last + self.duration = abs(last - first) + 1 + + if self.calendar_name: + + # Store the view parameters. + + self.previous_start = first.previous() + self.next_start = first.next() + self.previous_end = last.previous() + self.next_end = last.next() + + self.previous_set_start = first.update(-self.duration) + self.next_set_start = first.update(self.duration) + self.previous_set_end = last.update(-self.duration) + self.next_set_end = last.update(self.duration) + + def getIdentifier(self): + + "Return a unique identifier to be used to refer to this view." + + # NOTE: Nasty hack to get a unique identifier if no name is given. + + return self.calendar_name or str(id(self)) + + def getQualifiedParameterName(self, argname): + + "Return the 'argname' qualified using the calendar name." + + return getQualifiedParameterName(self.calendar_name, argname) + + def getDateQueryString(self, argname, date, prefix=1): + + """ + Return a query string fragment for the given 'argname', referring to the + month given by the specified 'year_month' object, appropriate for this + calendar. + + If 'prefix' is specified and set to a false value, the parameters in the + query string will not be calendar-specific, but could be used with the + summary action. + """ + + suffixes = ["year", "month", "day"] + + if date is not None: + args = [] + for suffix, value in zip(suffixes, date.as_tuple()): + suffixed_argname = "%s-%s" % (argname, suffix) + if prefix: + suffixed_argname = self.getQualifiedParameterName(suffixed_argname) + args.append("%s=%s" % (suffixed_argname, value)) + return "&".join(args) + else: + return "" + + def getRawDateQueryString(self, argname, date, prefix=1): + + """ + Return a query string fragment for the given 'argname', referring to the + date given by the specified 'date' value, appropriate for this + calendar. + + If 'prefix' is specified and set to a false value, the parameters in the + query string will not be calendar-specific, but could be used with the + summary action. + """ + + if date is not None: + if prefix: + argname = self.getQualifiedParameterName(argname) + return "%s=%s" % (argname, wikiutil.url_quote_plus(date)) + else: + return "" + + def getNavigationLink(self, start, end, mode=None, resolution=None, wider_start=None, wider_end=None): + + """ + Return a query string fragment for navigation to a view showing months + from 'start' to 'end' inclusive, with the optional 'mode' indicating the + view style and the optional 'resolution' indicating the resolution of a + view, if configurable. + + If the 'wider_start' and 'wider_end' arguments are given, parameters + indicating a wider calendar view (when returning from a day view, for + example) will be included in the link. + """ + + return "%s&%s&%s=%s&%s=%s&%s&%s" % ( + self.getRawDateQueryString("start", start), + self.getRawDateQueryString("end", end), + self.getQualifiedParameterName("mode"), mode or self.mode, + self.getQualifiedParameterName("resolution"), resolution or self.resolution, + self.getRawDateQueryString("wider-start", wider_start), + self.getRawDateQueryString("wider-end", wider_end), + ) + + def getUpdateLink(self, start, end, mode=None, resolution=None, wider_start=None, wider_end=None): + + """ + Return a query string fragment for navigation to a view showing months + from 'start' to 'end' inclusive, with the optional 'mode' indicating the + view style and the optional 'resolution' indicating the resolution of a + view, if configurable. This link differs from the conventional + navigation link in that it is sufficient to activate the update action + and produce an updated region of the page without needing to locate and + process the page or any macro invocation. + + If the 'wider_start' and 'wider_end' arguments are given, parameters + indicating a wider calendar view (when returning from a day view, for + example) will be included in the link. + """ + + parameters = [ + self.getRawDateQueryString("start", start, 0), + self.getRawDateQueryString("end", end, 0), + self.category_name_parameters, + self.remote_source_parameters, + self.getRawDateQueryString("wider-start", wider_start, 0), + self.getRawDateQueryString("wider-end", wider_end, 0), + ] + + pairs = [ + ("calendar", self.calendar_name or ""), + ("calendarstart", self.raw_calendar_start or ""), + ("calendarend", self.raw_calendar_end or ""), + ("mode", mode or self.mode), + ("resolution", resolution or self.resolution), + ("parent", self.parent_name or ""), + ("template", self.template_name or ""), + ("names", self.name_usage), + ("map", self.map_name or ""), + ("search", self.search_pattern or ""), + ] + + url = self.page.url(self.page.request, + "action=EventAggregatorUpdate&%s" % ( + "&".join([("%s=%s" % pair) for pair in pairs] + parameters) + ), relative=True) + + return "return replaceCalendar('EventAggregator-%s', '%s')" % (self.getIdentifier(), url) + + def getNewEventLink(self, start): + + """ + Return a query string activating the new event form, incorporating the + calendar parameters, specialising the form for the given 'start' date or + month. + """ + + if start is not None: + details = start.as_tuple() + pairs = zip(["start-year=%d", "start-month=%d", "start-day=%d"], details) + args = [(param % value) for (param, value) in pairs] + args = "&".join(args) + else: + args = "" + + # Prepare navigation details for the calendar shown with the new event + # form. + + navigation_link = self.getNavigationLink( + self.calendar_start, self.calendar_end + ) + + return "action=EventAggregatorNewEvent%s%s&template=%s&parent=%s&%s" % ( + args and "&%s" % args, + self.category_name_parameters and "&%s" % self.category_name_parameters, + self.template_name, self.parent_name or "", + navigation_link) + + def getFullDateLabel(self, date): + return getFullDateLabel(self.page.request, date) + + def getFullMonthLabel(self, year_month): + return getFullMonthLabel(self.page.request, year_month) + + def getFullLabel(self, arg): + return self.resolution == "date" and self.getFullDateLabel(arg) or self.getFullMonthLabel(arg) + + def _getCalendarPeriod(self, start_label, end_label, default_label): + + """ + Return a label describing a calendar period in terms of the given + 'start_label' and 'end_label', with the 'default_label' being used where + the supplied start and end labels fail to produce a meaningful label. + """ + + output = [] + append = output.append + + if start_label: + append(start_label) + if end_label and start_label != end_label: + if output: + append(" - ") + append(end_label) + return "".join(output) or default_label + + def getCalendarPeriod(self): + _ = self.page.request.getText + return self._getCalendarPeriod( + self.calendar_start and self.getFullLabel(self.calendar_start), + self.calendar_end and self.getFullLabel(self.calendar_end), + _("All events") + ) + + def getOriginalCalendarPeriod(self): + _ = self.page.request.getText + return self._getCalendarPeriod( + self.original_calendar_start and self.getFullLabel(self.original_calendar_start), + self.original_calendar_end and self.getFullLabel(self.original_calendar_end), + _("All events") + ) + + def getRawCalendarPeriod(self): + _ = self.page.request.getText + return self._getCalendarPeriod( + self.raw_calendar_start, + self.raw_calendar_end, + _("No period specified") + ) + + def writeDownloadControls(self): + + """ + Return a representation of the download controls, featuring links for + view, calendar and customised downloads and subscriptions. + """ + + page = self.page + request = page.request + fmt = request.formatter + _ = request.getText + + output = [] + append = output.append + + # The full URL is needed for webcal links. + + full_url = "%s%s" % (request.getBaseURL(), getPathInfo(request)) + + # Generate the links. + + download_dialogue_link = "action=EventAggregatorSummary&parent=%s&resolution=%s&search=%s%s%s" % ( + self.parent_name or "", + self.resolution, + self.search_pattern or "", + self.category_name_parameters and "&%s" % self.category_name_parameters, + self.remote_source_parameters and "&%s" % self.remote_source_parameters + ) + download_all_link = download_dialogue_link + "&doit=1" + download_link = download_all_link + ("&%s&%s" % ( + self.getDateQueryString("start", self.calendar_start, prefix=0), + self.getDateQueryString("end", self.calendar_end, prefix=0) + )) + + # Subscription links just explicitly select the RSS format. + + subscribe_dialogue_link = download_dialogue_link + "&format=RSS" + subscribe_all_link = download_all_link + "&format=RSS" + subscribe_link = download_link + "&format=RSS" + + # Adjust the "download all" and "subscribe all" links if the calendar + # has an inherent period associated with it. + + period_limits = [] + + if self.raw_calendar_start: + period_limits.append("&%s" % + self.getRawDateQueryString("start", self.raw_calendar_start, prefix=0) + ) + if self.raw_calendar_end: + period_limits.append("&%s" % + self.getRawDateQueryString("end", self.raw_calendar_end, prefix=0) + ) + + period_limits = "".join(period_limits) + + download_dialogue_link += period_limits + download_all_link += period_limits + subscribe_dialogue_link += period_limits + subscribe_all_link += period_limits + + # Pop-up descriptions of the downloadable calendars. + + calendar_period = self.getCalendarPeriod() + original_calendar_period = self.getOriginalCalendarPeriod() + raw_calendar_period = self.getRawCalendarPeriod() + + # Write the controls. + + # Download controls. + + append(fmt.div(on=1, css_class="event-download-controls")) + + append(fmt.span(on=1, css_class="event-download")) + append(fmt.text(_("Download..."))) + append(fmt.div(on=1, css_class="event-download-popup")) + + append(fmt.div(on=1, css_class="event-download-item")) + append(fmt.span(on=1, css_class="event-download-types")) + append(fmt.span(on=1, css_class="event-download-webcal")) + append(linkToResource(full_url.replace("http", "webcal", 1), request, _("webcal"), download_link)) + append(fmt.span(on=0)) + append(fmt.span(on=1, css_class="event-download-http")) + append(linkToPage(request, page, _("http"), download_link)) + append(fmt.span(on=0)) + append(fmt.span(on=0)) # end types + append(fmt.span(on=1, css_class="event-download-label")) + append(fmt.text(_("Download this view"))) + append(fmt.span(on=0)) # end label + append(fmt.span(on=1, css_class="event-download-period")) + append(fmt.text(calendar_period)) + append(fmt.span(on=0)) + append(fmt.div(on=0)) + + append(fmt.div(on=1, css_class="event-download-item")) + append(fmt.span(on=1, css_class="event-download-types")) + append(fmt.span(on=1, css_class="event-download-webcal")) + append(linkToResource(full_url.replace("http", "webcal", 1), request, _("webcal"), download_all_link)) + append(fmt.span(on=0)) + append(fmt.span(on=1, css_class="event-download-http")) + append(linkToPage(request, page, _("http"), download_all_link)) + append(fmt.span(on=0)) + append(fmt.span(on=0)) # end types + append(fmt.span(on=1, css_class="event-download-label")) + append(fmt.text(_("Download this calendar"))) + append(fmt.span(on=0)) # end label + append(fmt.span(on=1, css_class="event-download-period")) + append(fmt.text(original_calendar_period)) + append(fmt.span(on=0)) + append(fmt.span(on=1, css_class="event-download-period-raw")) + append(fmt.text(raw_calendar_period)) + append(fmt.span(on=0)) + append(fmt.div(on=0)) + + append(fmt.div(on=1, css_class="event-download-item")) + append(fmt.span(on=1, css_class="event-download-link")) + append(linkToPage(request, page, _("Edit download options..."), download_dialogue_link)) + append(fmt.span(on=0)) # end label + append(fmt.div(on=0)) + + append(fmt.div(on=0)) # end of pop-up + append(fmt.span(on=0)) # end of download + + # Subscription controls. + + append(fmt.span(on=1, css_class="event-download")) + append(fmt.text(_("Subscribe..."))) + append(fmt.div(on=1, css_class="event-download-popup")) + + append(fmt.div(on=1, css_class="event-download-item")) + append(fmt.span(on=1, css_class="event-download-label")) + append(linkToPage(request, page, _("Subscribe to this view"), subscribe_link)) + append(fmt.span(on=0)) # end label + append(fmt.span(on=1, css_class="event-download-period")) + append(fmt.text(calendar_period)) + append(fmt.span(on=0)) + append(fmt.div(on=0)) + + append(fmt.div(on=1, css_class="event-download-item")) + append(fmt.span(on=1, css_class="event-download-label")) + append(linkToPage(request, page, _("Subscribe to this calendar"), subscribe_all_link)) + append(fmt.span(on=0)) # end label + append(fmt.span(on=1, css_class="event-download-period")) + append(fmt.text(original_calendar_period)) + append(fmt.span(on=0)) + append(fmt.span(on=1, css_class="event-download-period-raw")) + append(fmt.text(raw_calendar_period)) + append(fmt.span(on=0)) + append(fmt.div(on=0)) + + append(fmt.div(on=1, css_class="event-download-item")) + append(fmt.span(on=1, css_class="event-download-link")) + append(linkToPage(request, page, _("Edit subscription options..."), subscribe_dialogue_link)) + append(fmt.span(on=0)) # end label + append(fmt.div(on=0)) + + append(fmt.div(on=0)) # end of pop-up + append(fmt.span(on=0)) # end of download + + append(fmt.div(on=0)) # end of controls + + return "".join(output) + + def writeViewControls(self): + + """ + Return a representation of the view mode controls, permitting viewing of + aggregated events in calendar, list or table form. + """ + + page = self.page + request = page.request + fmt = request.formatter + _ = request.getText + + output = [] + append = output.append + + # For day view links to other views, the wider view parameters should + # be used in order to be able to return to those other views. + + specific_start = self.calendar_start + specific_end = self.calendar_end + + start = self.wider_calendar_start or self.original_calendar_start and specific_start + end = self.wider_calendar_end or self.original_calendar_end and specific_end + + help_page = Page(request, "HelpOnEventAggregator") + + calendar_link = self.getNavigationLink(start and start.as_month(), end and end.as_month(), "calendar", "month") + calendar_update_link = self.getUpdateLink(start and start.as_month(), end and end.as_month(), "calendar", "month") + list_link = self.getNavigationLink(start, end, "list", "month") + list_update_link = self.getUpdateLink(start, end, "list", "month") + table_link = self.getNavigationLink(start, end, "table", "month") + table_update_link = self.getUpdateLink(start, end, "table", "month") + map_link = self.getNavigationLink(start, end, "map", "month") + map_update_link = self.getUpdateLink(start, end, "map", "month") + + # Specific links permit date-level navigation. + + specific_day_link = self.getNavigationLink(specific_start, specific_end, "day", wider_start=start, wider_end=end) + specific_day_update_link = self.getUpdateLink(specific_start, specific_end, "day", wider_start=start, wider_end=end) + specific_list_link = self.getNavigationLink(specific_start, specific_end, "list", wider_start=start, wider_end=end) + specific_list_update_link = self.getUpdateLink(specific_start, specific_end, "list", wider_start=start, wider_end=end) + specific_table_link = self.getNavigationLink(specific_start, specific_end, "table", wider_start=start, wider_end=end) + specific_table_update_link = self.getUpdateLink(specific_start, specific_end, "table", wider_start=start, wider_end=end) + specific_map_link = self.getNavigationLink(specific_start, specific_end, "map", wider_start=start, wider_end=end) + specific_map_update_link = self.getUpdateLink(specific_start, specific_end, "map", wider_start=start, wider_end=end) + + new_event_link = self.getNewEventLink(start) + + # Write the controls. + + append(fmt.div(on=1, css_class="event-view-controls")) + + append(fmt.span(on=1, css_class="event-view")) + append(linkToPage(request, help_page, _("Help"))) + append(fmt.span(on=0)) + + append(fmt.span(on=1, css_class="event-view")) + append(linkToPage(request, page, _("New event"), new_event_link)) + append(fmt.span(on=0)) + + if self.mode != "calendar": + view_label = self.resolution == "date" and _("View day in calendar") or _("View as calendar") + append(fmt.span(on=1, css_class="event-view")) + append(linkToPage(request, page, view_label, calendar_link, onclick=calendar_update_link)) + append(fmt.span(on=0)) + + if self.resolution == "date" and self.mode != "day": + append(fmt.span(on=1, css_class="event-view")) + append(linkToPage(request, page, _("View day as calendar"), specific_day_link, onclick=specific_day_update_link)) + append(fmt.span(on=0)) + + if self.resolution != "date" and self.mode != "list" or self.resolution == "date": + view_label = self.resolution == "date" and _("View day in list") or _("View as list") + append(fmt.span(on=1, css_class="event-view")) + append(linkToPage(request, page, view_label, list_link, onclick=list_update_link)) + append(fmt.span(on=0)) + + if self.resolution == "date" and self.mode != "list": + append(fmt.span(on=1, css_class="event-view")) + append(linkToPage(request, page, _("View day as list"), + specific_list_link, onclick=specific_list_update_link + )) + append(fmt.span(on=0)) + + if self.resolution != "date" and self.mode != "table" or self.resolution == "date": + view_label = self.resolution == "date" and _("View day in table") or _("View as table") + append(fmt.span(on=1, css_class="event-view")) + append(linkToPage(request, page, view_label, table_link, onclick=table_update_link)) + append(fmt.span(on=0)) + + if self.resolution == "date" and self.mode != "table": + append(fmt.span(on=1, css_class="event-view")) + append(linkToPage(request, page, _("View day as table"), + specific_table_link, onclick=specific_table_update_link + )) + append(fmt.span(on=0)) + + if self.map_name: + if self.resolution != "date" and self.mode != "map" or self.resolution == "date": + view_label = self.resolution == "date" and _("View day in map") or _("View as map") + append(fmt.span(on=1, css_class="event-view")) + append(linkToPage(request, page, view_label, map_link, onclick=map_update_link)) + append(fmt.span(on=0)) + + if self.resolution == "date" and self.mode != "map": + append(fmt.span(on=1, css_class="event-view")) + append(linkToPage(request, page, _("View day as map"), + specific_map_link, onclick=specific_map_update_link + )) + append(fmt.span(on=0)) + + append(fmt.div(on=0)) + + return "".join(output) + + def writeMapHeading(self): + + """ + Return the calendar heading for the current calendar, providing links + permitting navigation to other periods. + """ + + label = self.getCalendarPeriod() + + if self.raw_calendar_start is None or self.raw_calendar_end is None: + fmt = self.page.request.formatter + output = [] + append = output.append + append(fmt.span(on=1)) + append(fmt.text(label)) + append(fmt.span(on=0)) + return "".join(output) + else: + return self._writeCalendarHeading(label, self.calendar_start, self.calendar_end) + + def writeDateHeading(self, date): + if isinstance(date, Date): + return self.writeDayHeading(date) + else: + return self.writeMonthHeading(date) + + def writeMonthHeading(self, year_month): + + """ + Return the calendar heading for the given 'year_month' (a Month object) + providing links permitting navigation to other months. + """ + + full_month_label = self.getFullMonthLabel(year_month) + end_month = year_month.update(self.duration - 1) + return self._writeCalendarHeading(full_month_label, year_month, end_month) + + def writeDayHeading(self, date): + + """ + Return the calendar heading for the given 'date' (a Date object) + providing links permitting navigation to other dates. + """ + + full_date_label = self.getFullDateLabel(date) + end_date = date.update(self.duration - 1) + return self._writeCalendarHeading(full_date_label, date, end_date) + + def _writeCalendarHeading(self, label, start, end): + + """ + Write a calendar heading providing links permitting navigation to other + periods, using the given 'label' along with the 'start' and 'end' dates + to provide a link to a particular period. + """ + + page = self.page + request = page.request + fmt = request.formatter + _ = request.getText + + output = [] + append = output.append + + # Prepare navigation links. + + if self.calendar_name: + calendar_name = self.calendar_name + + # Links to the previous set of months and to a calendar shifted + # back one month. + + previous_set_link = self.getNavigationLink( + self.previous_set_start, self.previous_set_end + ) + previous_link = self.getNavigationLink( + self.previous_start, self.previous_end + ) + previous_set_update_link = self.getUpdateLink( + self.previous_set_start, self.previous_set_end + ) + previous_update_link = self.getUpdateLink( + self.previous_start, self.previous_end + ) + + # Links to the next set of months and to a calendar shifted + # forward one month. + + next_set_link = self.getNavigationLink( + self.next_set_start, self.next_set_end + ) + next_link = self.getNavigationLink( + self.next_start, self.next_end + ) + next_set_update_link = self.getUpdateLink( + self.next_set_start, self.next_set_end + ) + next_update_link = self.getUpdateLink( + self.next_start, self.next_end + ) + + # A link leading to this date being at the top of the calendar. + + date_link = self.getNavigationLink(start, end) + date_update_link = self.getUpdateLink(start, end) + + append(fmt.span(on=1, css_class="previous")) + append(linkToPage(request, page, "<<", previous_set_link, onclick=previous_set_update_link)) + append(fmt.text(" ")) + append(linkToPage(request, page, "<", previous_link, onclick=previous_update_link)) + append(fmt.span(on=0)) + + append(fmt.span(on=1, css_class="next")) + append(linkToPage(request, page, ">", next_link, onclick=next_update_link)) + append(fmt.text(" ")) + append(linkToPage(request, page, ">>", next_set_link, onclick=next_set_update_link)) + append(fmt.span(on=0)) + + append(linkToPage(request, page, label, date_link, onclick=date_update_link)) + + else: + append(fmt.span(on=1)) + append(fmt.text(label)) + append(fmt.span(on=0)) + + return "".join(output) + + def writeDayNumberHeading(self, date, busy): + + """ + Return a link for the given 'date' which will activate the new event + action for the given day. If 'busy' is given as a true value, the + heading will be marked as busy. + """ + + page = self.page + request = page.request + fmt = request.formatter + _ = request.getText + + output = [] + append = output.append + + year, month, day = date.as_tuple() + new_event_link = self.getNewEventLink(date) + + # Prepare a link to the day view for this day. + + day_view_link = self.getNavigationLink(date, date, "day", "date", self.calendar_start, self.calendar_end) + day_view_update_link = self.getUpdateLink(date, date, "day", "date", self.calendar_start, self.calendar_end) + + # Output the heading class. + + today_attr = date == getCurrentDate() and "event-day-current" or "" + + append( + fmt.table_cell(on=1, attrs={ + "class" : "event-day-heading event-day-%s %s" % (busy and "busy" or "empty", today_attr), + "colspan" : "3" + })) + + # Output the number and pop-up menu. + + append(fmt.div(on=1, css_class="event-day-box")) + + append(fmt.span(on=1, css_class="event-day-number-popup")) + append(fmt.span(on=1, css_class="event-day-number-link")) + append(linkToPage(request, page, _("View day"), day_view_link, onclick=day_view_update_link)) + append(fmt.span(on=0)) + append(fmt.span(on=1, css_class="event-day-number-link")) + append(linkToPage(request, page, _("New event"), new_event_link)) + append(fmt.span(on=0)) + append(fmt.span(on=0)) + + append(fmt.span(on=1, css_class="event-day-number")) + append(fmt.text(unicode(day))) + append(fmt.span(on=0)) + + append(fmt.div(on=0)) + + # End of heading. + + append(fmt.table_cell(on=0)) + + return "".join(output) + + # Common layout methods. + + def getEventStyle(self, colour_seed): + + "Generate colour style information using the given 'colour_seed'." + + bg = getColour(colour_seed) + fg = getBlackOrWhite(bg) + return "background-color: rgb(%d, %d, %d); color: rgb(%d, %d, %d);" % (bg + fg) + + def writeEventSummaryBox(self, event): + + "Return an event summary box linking to the given 'event'." + + page = self.page + request = page.request + fmt = request.formatter + + output = [] + append = output.append + + event_details = event.getDetails() + event_summary = event.getSummary(self.parent_name) + + is_ambiguous = event.as_timespan().ambiguous() + style = self.getEventStyle(event_summary) + + # The event box contains the summary, alongside + # other elements. + + append(fmt.div(on=1, css_class="event-summary-box")) + append(fmt.div(on=1, css_class="event-summary", style=style)) + + if is_ambiguous: + append(fmt.icon("/!\\")) + + append(event.linkToEvent(request, event_summary)) + append(fmt.div(on=0)) + + # Add a pop-up element for long summaries. + + append(fmt.div(on=1, css_class="event-summary-popup", style=style)) + + if is_ambiguous: + append(fmt.icon("/!\\")) + + append(event.linkToEvent(request, event_summary)) + append(fmt.div(on=0)) + + append(fmt.div(on=0)) + + return "".join(output) + + # Calendar layout methods. + + def writeMonthTableHeading(self, year_month): + page = self.page + fmt = page.request.formatter + + output = [] + append = output.append + + append(fmt.table_row(on=1)) + append(fmt.table_cell(on=1, attrs={"class" : "event-month-heading", "colspan" : "21"})) + + append(self.writeMonthHeading(year_month)) + + append(fmt.table_cell(on=0)) + append(fmt.table_row(on=0)) + + return "".join(output) + + def writeWeekdayHeadings(self): + page = self.page + request = page.request + fmt = request.formatter + _ = request.getText + + output = [] + append = output.append + + append(fmt.table_row(on=1)) + + for weekday in range(0, 7): + append(fmt.table_cell(on=1, attrs={"class" : "event-weekday-heading", "colspan" : "3"})) + append(fmt.text(_(getDayLabel(weekday)))) + append(fmt.table_cell(on=0)) + + append(fmt.table_row(on=0)) + return "".join(output) + + def writeDayNumbers(self, first_day, number_of_days, month, coverage): + page = self.page + fmt = page.request.formatter + + output = [] + append = output.append + + append(fmt.table_row(on=1)) + + for weekday in range(0, 7): + day = first_day + weekday + date = month.as_date(day) + + # Output out-of-month days. + + if day < 1 or day > number_of_days: + append(fmt.table_cell(on=1, + attrs={"class" : "event-day-heading event-day-excluded", "colspan" : "3"})) + append(fmt.table_cell(on=0)) + + # Output normal days. + + else: + # Output the day heading, making a link to a new event + # action. + + append(self.writeDayNumberHeading(date, date in coverage)) + + # End of day numbers. + + append(fmt.table_row(on=0)) + return "".join(output) + + def writeEmptyWeek(self, first_day, number_of_days, month): + page = self.page + fmt = page.request.formatter + + output = [] + append = output.append + + append(fmt.table_row(on=1)) + + for weekday in range(0, 7): + day = first_day + weekday + date = month.as_date(day) + + today_attr = date == getCurrentDate() and "event-day-current" or "" + + # Output out-of-month days. + + if day < 1 or day > number_of_days: + append(fmt.table_cell(on=1, + attrs={"class" : "event-day-content event-day-excluded %s" % today_attr, "colspan" : "3"})) + append(fmt.table_cell(on=0)) + + # Output empty days. + + else: + append(fmt.table_cell(on=1, + attrs={"class" : "event-day-content event-day-empty %s" % today_attr, "colspan" : "3"})) + + append(fmt.table_row(on=0)) + return "".join(output) + + def writeWeekSlots(self, first_day, number_of_days, month, week_end, week_slots): + output = [] + append = output.append + + locations = week_slots.keys() + locations.sort(sort_none_first) + + # Visit each slot corresponding to a location (or no location). + + for location in locations: + + # Visit each coverage span, presenting the events in the span. + + for events in week_slots[location]: + + # Output each set. + + append(self.writeWeekSlot(first_day, number_of_days, month, week_end, events)) + + # Add a spacer. + + append(self.writeWeekSpacer(first_day, number_of_days, month)) + + return "".join(output) + + def writeWeekSlot(self, first_day, number_of_days, month, week_end, events): + page = self.page + request = page.request + fmt = request.formatter + + output = [] + append = output.append + + append(fmt.table_row(on=1)) + + # Then, output day details. + + for weekday in range(0, 7): + day = first_day + weekday + date = month.as_date(day) + + # Skip out-of-month days. + + if day < 1 or day > number_of_days: + append(fmt.table_cell(on=1, + attrs={"class" : "event-day-content event-day-excluded", "colspan" : "3"})) + append(fmt.table_cell(on=0)) + continue + + # Output the day. + # Where a day does not contain an event, a single cell is used. + # Otherwise, multiple cells are used to provide space before, during + # and after events. + + today_attr = date == getCurrentDate() and "event-day-current" or "" + + if date not in events: + append(fmt.table_cell(on=1, + attrs={"class" : "event-day-content event-day-empty %s" % today_attr, "colspan" : "3"})) + + # Get event details for the current day. + + for event in events: + event_details = event.getDetails() + + if date not in event: + continue + + # Get basic properties of the event. + + starts_today = event_details["start"] == date + ends_today = event_details["end"] == date + event_summary = event.getSummary(self.parent_name) + + style = self.getEventStyle(event_summary) + + # Determine if the event name should be shown. + + start_of_period = starts_today or weekday == 0 or day == 1 + + if self.name_usage == "daily" or start_of_period: + hide_text = 0 + else: + hide_text = 1 + + # Output start of day gap and determine whether + # any event content should be explicitly output + # for this day. + + if starts_today: + + # Single day events... + + if ends_today: + colspan = 3 + event_day_type = "event-day-single" + + # Events starting today... + + else: + append(fmt.table_cell(on=1, attrs={"class" : "event-day-start-gap %s" % today_attr})) + append(fmt.table_cell(on=0)) + + # Calculate the span of this cell. + # Events whose names appear on every day... + + if self.name_usage == "daily": + colspan = 2 + event_day_type = "event-day-starting" + + # Events whose names appear once per week... + + else: + if event_details["end"] <= week_end: + event_length = event_details["end"].day() - day + 1 + colspan = (event_length - 2) * 3 + 4 + else: + event_length = week_end.day() - day + 1 + colspan = (event_length - 1) * 3 + 2 + + event_day_type = "event-day-multiple" + + # Events continuing from a previous week... + + elif start_of_period: + + # End of continuing event... + + if ends_today: + colspan = 2 + event_day_type = "event-day-ending" + + # Events continuing for at least one more day... + + else: + + # Calculate the span of this cell. + # Events whose names appear on every day... + + if self.name_usage == "daily": + colspan = 3 + event_day_type = "event-day-full" + + # Events whose names appear once per week... + + else: + if event_details["end"] <= week_end: + event_length = event_details["end"].day() - day + 1 + colspan = (event_length - 1) * 3 + 2 + else: + event_length = week_end.day() - day + 1 + colspan = event_length * 3 + + event_day_type = "event-day-multiple" + + # Continuing events whose names appear on every day... + + elif self.name_usage == "daily": + if ends_today: + colspan = 2 + event_day_type = "event-day-ending" + else: + colspan = 3 + event_day_type = "event-day-full" + + # Continuing events whose names appear once per week... + + else: + colspan = None + + # Output the main content only if it is not + # continuing from a previous day. + + if colspan is not None: + + # Colour the cell for continuing events. + + attrs={ + "class" : "event-day-content event-day-busy %s %s" % (event_day_type, today_attr), + "colspan" : str(colspan) + } + + if not (starts_today and ends_today): + attrs["style"] = style + + append(fmt.table_cell(on=1, attrs=attrs)) + + # Output the event. + + if starts_today and ends_today or not hide_text: + append(self.writeEventSummaryBox(event)) + + append(fmt.table_cell(on=0)) + + # Output end of day gap. + + if ends_today and not starts_today: + append(fmt.table_cell(on=1, attrs={"class" : "event-day-end-gap %s" % today_attr})) + append(fmt.table_cell(on=0)) + + # End of set. + + append(fmt.table_row(on=0)) + return "".join(output) + + def writeWeekSpacer(self, first_day, number_of_days, month): + page = self.page + fmt = page.request.formatter + + output = [] + append = output.append + + append(fmt.table_row(on=1)) + + for weekday in range(0, 7): + day = first_day + weekday + date = month.as_date(day) + today_attr = date == getCurrentDate() and "event-day-current" or "" + + css_classes = "event-day-spacer %s" % today_attr + + # Skip out-of-month days. + + if day < 1 or day > number_of_days: + css_classes += " event-day-excluded" + + append(fmt.table_cell(on=1, attrs={"class" : css_classes, "colspan" : "3"})) + append(fmt.table_cell(on=0)) + + append(fmt.table_row(on=0)) + return "".join(output) + + # Day layout methods. + + def writeDayTableHeading(self, date, colspan=1): + page = self.page + fmt = page.request.formatter + + output = [] + append = output.append + + append(fmt.table_row(on=1)) + + append(fmt.table_cell(on=1, attrs={"class" : "event-full-day-heading", "colspan" : str(colspan)})) + append(self.writeDayHeading(date)) + append(fmt.table_cell(on=0)) + + append(fmt.table_row(on=0)) + return "".join(output) + + def writeEmptyDay(self, date): + page = self.page + fmt = page.request.formatter + + output = [] + append = output.append + + append(fmt.table_row(on=1)) + + append(fmt.table_cell(on=1, + attrs={"class" : "event-day-content event-day-empty"})) + + append(fmt.table_row(on=0)) + return "".join(output) + + def writeDaySlots(self, date, full_coverage, day_slots): + + """ + Given a 'date', non-empty 'full_coverage' for the day concerned, and a + non-empty mapping of 'day_slots' (from locations to event collections), + output the day slots for the day. + """ + + page = self.page + fmt = page.request.formatter + + output = [] + append = output.append + + locations = day_slots.keys() + locations.sort(sort_none_first) + + # Traverse the time scale of the full coverage, visiting each slot to + # determine whether it provides content for each period. + + scale = getCoverageScale(full_coverage) + + # Define a mapping of events to rowspans. + + rowspans = {} + + # Populate each period with event details, recording how many periods + # each event populates. + + day_rows = [] + + for period in scale: + + # Ignore timespans before this day. + + if period != date: + continue + + # Visit each slot corresponding to a location (or no location). + + day_row = [] + + for location in locations: + + # Visit each coverage span, presenting the events in the span. + + for events in day_slots[location]: + event = self.getActiveEvent(period, events) + if event is not None: + if not rowspans.has_key(event): + rowspans[event] = 1 + else: + rowspans[event] += 1 + day_row.append((location, event)) + + day_rows.append((period, day_row)) + + # Output the locations. + + append(fmt.table_row(on=1)) + + # Add a spacer. + + append(self.writeDaySpacer(colspan=2, cls="location")) + + for location in locations: + + # Add spacers to the column spans. + + columns = len(day_slots[location]) * 2 - 1 + append(fmt.table_cell(on=1, attrs={"class" : "event-location-heading", "colspan" : str(columns)})) + append(fmt.text(location or "")) + append(fmt.table_cell(on=0)) + + # Add a trailing spacer. + + append(self.writeDaySpacer(cls="location")) + + append(fmt.table_row(on=0)) + + # Output the periods with event details. + + period = None + events_written = set() + + for period, day_row in day_rows: + + # Write an empty heading for the start of the day where the first + # applicable timespan starts before this day. + + if period.start < date: + append(fmt.table_row(on=1)) + append(self.writeDayScaleHeading("")) + + # Otherwise, write a heading describing the time. + + else: + append(fmt.table_row(on=1)) + append(self.writeDayScaleHeading(period.start.time_string())) + + append(self.writeDaySpacer()) + + # Visit each slot corresponding to a location (or no location). + + for location, event in day_row: + + # Output each location slot's contribution. + + if event is None or event not in events_written: + append(self.writeDaySlot(period, event, event is None and 1 or rowspans[event])) + if event is not None: + events_written.add(event) + + # Add a trailing spacer. + + append(self.writeDaySpacer()) + + append(fmt.table_row(on=0)) + + # Write a final time heading if the last period ends in the current day. + + if period is not None: + if period.end == date: + append(fmt.table_row(on=1)) + append(self.writeDayScaleHeading(period.end.time_string())) + + for slot in day_row: + append(self.writeDaySpacer()) + append(self.writeEmptyDaySlot()) + + append(fmt.table_row(on=0)) + + return "".join(output) + + def writeDayScaleHeading(self, heading): + page = self.page + fmt = page.request.formatter + + output = [] + append = output.append + + append(fmt.table_cell(on=1, attrs={"class" : "event-scale-heading"})) + append(fmt.text(heading)) + append(fmt.table_cell(on=0)) + + return "".join(output) + + def getActiveEvent(self, period, events): + for event in events: + if period not in event: + continue + return event + else: + return None + + def writeDaySlot(self, period, event, rowspan): + page = self.page + fmt = page.request.formatter + + output = [] + append = output.append + + if event is not None: + event_summary = event.getSummary(self.parent_name) + style = self.getEventStyle(event_summary) + + append(fmt.table_cell(on=1, attrs={ + "class" : "event-timespan-content event-timespan-busy", + "style" : style, + "rowspan" : str(rowspan) + })) + append(self.writeEventSummaryBox(event)) + append(fmt.table_cell(on=0)) + else: + append(self.writeEmptyDaySlot()) + + return "".join(output) + + def writeEmptyDaySlot(self): + page = self.page + fmt = page.request.formatter + + output = [] + append = output.append + + append(fmt.table_cell(on=1, + attrs={"class" : "event-timespan-content event-timespan-empty"})) + append(fmt.table_cell(on=0)) + + return "".join(output) + + def writeDaySpacer(self, colspan=1, cls="timespan"): + page = self.page + fmt = page.request.formatter + + output = [] + append = output.append + + append(fmt.table_cell(on=1, attrs={ + "class" : "event-%s-spacer" % cls, + "colspan" : str(colspan)})) + append(fmt.table_cell(on=0)) + return "".join(output) + + # Map layout methods. + + def writeMapTableHeading(self): + page = self.page + fmt = page.request.formatter + + output = [] + append = output.append + + append(fmt.table_cell(on=1, attrs={"class" : "event-map-heading"})) + append(self.writeMapHeading()) + append(fmt.table_cell(on=0)) + + return "".join(output) + + def showDictError(self, text, pagename): + page = self.page + request = page.request + fmt = request.formatter + + output = [] + append = output.append + + append(fmt.div(on=1, attrs={"class" : "event-aggregator-error"})) + append(fmt.paragraph(on=1)) + append(fmt.text(text)) + append(fmt.paragraph(on=0)) + append(fmt.paragraph(on=1)) + append(linkToPage(request, Page(request, pagename), pagename)) + append(fmt.paragraph(on=0)) + + return "".join(output) + + def writeMapMarker(self, marker_x, marker_y, map_x_scale, map_y_scale, location, events): + + "Put a marker on the map." + + page = self.page + request = page.request + fmt = request.formatter + + output = [] + append = output.append + + append(fmt.listitem(on=1, css_class="event-map-label")) + + # Have a positioned marker for the print mode. + + append(fmt.div(on=1, css_class="event-map-label-only", + style="left:%dpx; top:%dpx; min-width:%dpx; min-height:%dpx") % ( + marker_x, marker_y, map_x_scale, map_y_scale)) + append(fmt.div(on=0)) + + # Have a marker containing a pop-up when using the screen mode, + # providing a normal block when using the print mode. + + append(fmt.div(on=1, css_class="event-map-label", + style="left:%dpx; top:%dpx; min-width:%dpx; min-height:%dpx") % ( + marker_x, marker_y, map_x_scale, map_y_scale)) + append(fmt.div(on=1, css_class="event-map-details")) + append(fmt.div(on=1, css_class="event-map-shadow")) + append(fmt.div(on=1, css_class="event-map-location")) + + # The location may have been given as formatted text, but this will not + # be usable in a heading, so it must be first converted to plain text. + + append(fmt.heading(on=1, depth=2)) + append(fmt.text(to_plain_text(location, request))) + append(fmt.heading(on=0, depth=2)) + + append(self.writeMapEventSummaries(events)) + + append(fmt.div(on=0)) + append(fmt.div(on=0)) + append(fmt.div(on=0)) + append(fmt.div(on=0)) + append(fmt.listitem(on=0)) + + return "".join(output) + + def writeMapEventSummaries(self, events): + + "Write summaries of the given 'events' for the map." + + page = self.page + request = page.request + fmt = request.formatter + + # Sort the events by date. + + events.sort(sort_start_first) + + # Write out a self-contained list of events. + + output = [] + append = output.append + + append(fmt.bullet_list(on=1, attr={"class" : "event-map-location-events"})) + + for event in events: + + # Get the event details. + + event_summary = event.getSummary(self.parent_name) + start, end = event.as_limits() + event_period = self._getCalendarPeriod( + start and self.getFullDateLabel(start), + end and self.getFullDateLabel(end), + "") + + append(fmt.listitem(on=1)) + + # Link to the page using the summary. + + append(event.linkToEvent(request, event_summary)) + + # Add the event period. + + append(fmt.text(" ")) + append(fmt.span(on=1, css_class="event-map-period")) + append(fmt.text(event_period)) + append(fmt.span(on=0)) + + append(fmt.listitem(on=0)) + + append(fmt.bullet_list(on=0)) + + return "".join(output) + + def render(self, all_shown_events): + + """ + Render the view, returning the rendered representation as a string. + The view will show a list of 'all_shown_events'. + """ + + page = self.page + request = page.request + fmt = request.formatter + _ = request.getText + + # Make a calendar. + + output = [] + append = output.append + + append(fmt.div(on=1, css_class="event-calendar", id=("EventAggregator-%s" % self.getIdentifier()))) + + # Output download controls. + + append(fmt.div(on=1, css_class="event-controls")) + append(self.writeDownloadControls()) + append(fmt.div(on=0)) + + # Output a table. + + if self.mode == "table": + + # Start of table view output. + + append(fmt.table(on=1, attrs={"tableclass" : "event-table"})) + + append(fmt.table_row(on=1)) + append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) + append(fmt.text(_("Event dates"))) + append(fmt.table_cell(on=0)) + append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) + append(fmt.text(_("Event location"))) + append(fmt.table_cell(on=0)) + append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"})) + append(fmt.text(_("Event details"))) + append(fmt.table_cell(on=0)) + append(fmt.table_row(on=0)) + + # Show the events in order. + + all_shown_events.sort(sort_start_first) + + for event in all_shown_events: + event_page = event.getPage() + event_summary = event.getSummary(self.parent_name) + event_details = event.getDetails() + + # Prepare CSS classes with category-related styling. + + css_classes = ["event-table-details"] + + for topic in event_details.get("topics") or event_details.get("categories") or []: + + # Filter the category text to avoid illegal characters. + + css_classes.append("event-table-category-%s" % "".join(filter(lambda c: c.isalnum(), topic))) + + attrs = {"class" : " ".join(css_classes)} + + append(fmt.table_row(on=1)) + + # Start and end dates. + + append(fmt.table_cell(on=1, attrs=attrs)) + append(fmt.span(on=1)) + append(fmt.text(str(event_details["start"]))) + append(fmt.span(on=0)) + + if event_details["start"] != event_details["end"]: + append(fmt.text(" - ")) + append(fmt.span(on=1)) + append(fmt.text(str(event_details["end"]))) + append(fmt.span(on=0)) + + append(fmt.table_cell(on=0)) + + # Location. + + append(fmt.table_cell(on=1, attrs=attrs)) + + if event_details.has_key("location"): + append(event_page.formatText(event_details["location"], fmt)) + + append(fmt.table_cell(on=0)) + + # Link to the page using the summary. + + append(fmt.table_cell(on=1, attrs=attrs)) + append(event.linkToEvent(request, event_summary)) + append(fmt.table_cell(on=0)) + + append(fmt.table_row(on=0)) + + # End of table view output. + + append(fmt.table(on=0)) + + # Output a map view. + + elif self.mode == "map": + + # Special dictionary pages. + + maps_page = getMapsPage(request) + locations_page = getLocationsPage(request) + + map_image = None + + # Get the maps and locations. + + maps = getWikiDict(maps_page, request) + locations = getWikiDict(locations_page, request) + + # Get the map image definition. + + if maps is not None and self.map_name: + try: + map_details = maps[self.map_name].split() + + map_bottom_left_latitude, map_bottom_left_longitude, map_top_right_latitude, map_top_right_longitude = \ + map(getMapReference, map_details[:4]) + map_width, map_height = map(int, map_details[4:6]) + map_image = map_details[6] + + map_x_scale = map_width / (map_top_right_longitude - map_bottom_left_longitude).to_degrees() + map_y_scale = map_height / (map_top_right_latitude - map_bottom_left_latitude).to_degrees() + + except (KeyError, ValueError): + pass + + # Report errors. + + if maps is None: + append(self.showDictError( + _("You do not have read access to the maps page:"), + maps_page)) + + elif not self.map_name: + append(self.showDictError( + _("Please specify a valid map name corresponding to an entry on the following page:"), + maps_page)) + + elif map_image is None: + append(self.showDictError( + _("Please specify a valid entry for %s on the following page:") % self.map_name, + maps_page)) + + elif locations is None: + append(self.showDictError( + _("You do not have read access to the locations page:"), + locations_page)) + + # Attempt to show the map. + + else: + + # Get events by position. + + events_by_location = {} + event_locations = {} + + for event in all_shown_events: + event_details = event.getDetails() + + location = event_details.get("location") + geo = event_details.get("geo") + + # Make a temporary location if an explicit position is given + # but not a location name. + + if not location and geo: + location = "%s %s" % tuple(geo) + + # Map the location to a position. + + if location is not None and not event_locations.has_key(location): + + # Get any explicit position of an event. + + if geo: + latitude, longitude = geo + + # Or look up the position of a location using the locations + # page. + + else: + latitude, longitude = Location(location, locations).getPosition() + + # Use a normalised location if necessary. + + if latitude is None and longitude is None: + normalised_location = getNormalisedLocation(location) + if normalised_location is not None: + latitude, longitude = getLocationPosition(normalised_location, locations) + if latitude is not None and longitude is not None: + location = normalised_location + + # Only remember positioned locations. + + if latitude is not None and longitude is not None: + event_locations[location] = latitude, longitude + + # Record events according to location. + + if not events_by_location.has_key(location): + events_by_location[location] = [] + + events_by_location[location].append(event) + + # Get the map image URL. + + map_image_url = AttachFile.getAttachUrl(maps_page, map_image, request) + + # Start of map view output. + + map_identifier = "map-%s" % self.getIdentifier() + append(fmt.div(on=1, css_class="event-map", id=map_identifier)) + + append(fmt.table(on=1)) + + append(fmt.table_row(on=1)) + append(self.writeMapTableHeading()) + append(fmt.table_row(on=0)) + + append(fmt.table_row(on=1)) + append(fmt.table_cell(on=1)) + + append(fmt.div(on=1, css_class="event-map-container")) + append(fmt.image(map_image_url)) + append(fmt.number_list(on=1)) + + # Events with no location are unpositioned. + + if events_by_location.has_key(None): + unpositioned_events = events_by_location[None] + del events_by_location[None] + else: + unpositioned_events = [] + + # Events whose location is unpositioned are themselves considered + # unpositioned. + + for location in set(events_by_location.keys()).difference(event_locations.keys()): + unpositioned_events += events_by_location[location] + + # Sort the locations before traversing them. + + event_locations = event_locations.items() + event_locations.sort() + + # Show the events in the map. + + for location, (latitude, longitude) in event_locations: + events = events_by_location[location] + + # Skip unpositioned locations and locations outside the map. + + if latitude is None or longitude is None or \ + latitude < map_bottom_left_latitude or \ + longitude < map_bottom_left_longitude or \ + latitude > map_top_right_latitude or \ + longitude > map_top_right_longitude: + + unpositioned_events += events + continue + + # Get the position and dimensions of the map marker. + # NOTE: Use one degree as the marker size. + + marker_x, marker_y = getPositionForCentrePoint( + getPositionForReference(map_top_right_latitude, longitude, latitude, map_bottom_left_longitude, + map_x_scale, map_y_scale), + map_x_scale, map_y_scale) + + # Add the map marker. + + append(self.writeMapMarker(marker_x, marker_y, map_x_scale, map_y_scale, location, events)) + + append(fmt.number_list(on=0)) + append(fmt.div(on=0)) + append(fmt.table_cell(on=0)) + append(fmt.table_row(on=0)) + + # Write unpositioned events. + + if unpositioned_events: + unpositioned_identifier = "unpositioned-%s" % self.getIdentifier() + + append(fmt.table_row(on=1, css_class="event-map-unpositioned", + id=unpositioned_identifier)) + append(fmt.table_cell(on=1)) + + append(fmt.heading(on=1, depth=2)) + append(fmt.text(_("Events not shown on the map"))) + append(fmt.heading(on=0, depth=2)) + + # Show and hide controls. + + append(fmt.div(on=1, css_class="event-map-show-control")) + append(fmt.anchorlink(on=1, name=unpositioned_identifier)) + append(fmt.text(_("Show unpositioned events"))) + append(fmt.anchorlink(on=0)) + append(fmt.div(on=0)) + + append(fmt.div(on=1, css_class="event-map-hide-control")) + append(fmt.anchorlink(on=1, name=map_identifier)) + append(fmt.text(_("Hide unpositioned events"))) + append(fmt.anchorlink(on=0)) + append(fmt.div(on=0)) + + append(self.writeMapEventSummaries(unpositioned_events)) + + # End of map view output. + + append(fmt.table_cell(on=0)) + append(fmt.table_row(on=0)) + append(fmt.table(on=0)) + append(fmt.div(on=0)) + + # Output a list. + + elif self.mode == "list": + + # Start of list view output. + + append(fmt.bullet_list(on=1, attr={"class" : "event-listings"})) + + # Output a list. + + for period in self.first.until(self.last): + + append(fmt.listitem(on=1, attr={"class" : "event-listings-period"})) + append(fmt.div(on=1, attr={"class" : "event-listings-heading"})) + + # Either write a date heading or produce links for navigable + # calendars. + + append(self.writeDateHeading(period)) + + append(fmt.div(on=0)) + + append(fmt.bullet_list(on=1, attr={"class" : "event-period-listings"})) + + # Show the events in order. + + events_in_period = getEventsInPeriod(all_shown_events, getCalendarPeriod(period, period)) + events_in_period.sort(sort_start_first) + + for event in events_in_period: + event_page = event.getPage() + event_details = event.getDetails() + event_summary = event.getSummary(self.parent_name) + + append(fmt.listitem(on=1, attr={"class" : "event-listing"})) + + # Link to the page using the summary. + + append(fmt.paragraph(on=1)) + append(event.linkToEvent(request, event_summary)) + append(fmt.paragraph(on=0)) + + # Start and end dates. + + append(fmt.paragraph(on=1)) + append(fmt.span(on=1)) + append(fmt.text(str(event_details["start"]))) + append(fmt.span(on=0)) + append(fmt.text(" - ")) + append(fmt.span(on=1)) + append(fmt.text(str(event_details["end"]))) + append(fmt.span(on=0)) + append(fmt.paragraph(on=0)) + + # Location. + + if event_details.has_key("location"): + append(fmt.paragraph(on=1)) + append(event_page.formatText(event_details["location"], fmt)) + append(fmt.paragraph(on=1)) + + # Topics. + + if event_details.has_key("topics") or event_details.has_key("categories"): + append(fmt.bullet_list(on=1, attr={"class" : "event-topics"})) + + for topic in event_details.get("topics") or event_details.get("categories") or []: + append(fmt.listitem(on=1)) + append(event_page.formatText(topic, fmt)) + append(fmt.listitem(on=0)) + + append(fmt.bullet_list(on=0)) + + append(fmt.listitem(on=0)) + + append(fmt.bullet_list(on=0)) + + # End of list view output. + + append(fmt.bullet_list(on=0)) + + # Output a month calendar. This shows month-by-month data. + + elif self.mode == "calendar": + + # Visit all months in the requested range, or across known events. + + for month in self.first.months_until(self.last): + + # Output a month. + + append(fmt.table(on=1, attrs={"tableclass" : "event-month"})) + + # Either write a month heading or produce links for navigable + # calendars. + + append(self.writeMonthTableHeading(month)) + + # Weekday headings. + + append(self.writeWeekdayHeadings()) + + # Process the days of the month. + + start_weekday, number_of_days = month.month_properties() + + # The start weekday is the weekday of day number 1. + # Find the first day of the week, counting from below zero, if + # necessary, in order to land on the first day of the month as + # day number 1. + + first_day = 1 - start_weekday + + while first_day <= number_of_days: + + # Find events in this week and determine how to mark them on the + # calendar. + + week_start = month.as_date(max(first_day, 1)) + week_end = month.as_date(min(first_day + 6, number_of_days)) + + full_coverage, week_slots = getCoverage( + getEventsInPeriod(all_shown_events, getCalendarPeriod(week_start, week_end))) + + # Output a week, starting with the day numbers. + + append(self.writeDayNumbers(first_day, number_of_days, month, full_coverage)) + + # Either generate empty days... + + if not week_slots: + append(self.writeEmptyWeek(first_day, number_of_days, month)) + + # Or generate each set of scheduled events... + + else: + append(self.writeWeekSlots(first_day, number_of_days, month, week_end, week_slots)) + + # Process the next week... + + first_day += 7 + + # End of month. + + append(fmt.table(on=0)) + + # Output a day view. + + elif self.mode == "day": + + # Visit all days in the requested range, or across known events. + + for date in self.first.days_until(self.last): + + append(fmt.table(on=1, attrs={"tableclass" : "event-calendar-day"})) + + full_coverage, day_slots = getCoverage( + getEventsInPeriod(all_shown_events, getCalendarPeriod(date, date)), "datetime") + + # Work out how many columns the day title will need. + # Include spacers after the scale and each event column. + + colspan = sum(map(len, day_slots.values())) * 2 + 2 + + append(self.writeDayTableHeading(date, colspan)) + + # Either generate empty days... + + if not day_slots: + append(self.writeEmptyDay(date)) + + # Or generate each set of scheduled events... + + else: + append(self.writeDaySlots(date, full_coverage, day_slots)) + + # End of day. + + append(fmt.table(on=0)) + + # Output view controls. + + append(fmt.div(on=1, css_class="event-controls")) + append(self.writeViewControls()) + append(fmt.div(on=0)) + + # Close the calendar region. + + append(fmt.div(on=0)) + + # Add any scripts. + + if isinstance(fmt, request.html_formatter.__class__): + append(self.update_script) + + return ''.join(output) + + update_script = """\ + +""" + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r cfb2743a9f38 -r 693940d48e8b EventAggregatorSupport/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/EventAggregatorSupport/__init__.py Tue Apr 30 23:49:32 2013 +0200 @@ -0,0 +1,18 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - EventAggregator library + + @copyright: 2013 by Paul Boddie + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +from EventAggregatorSupport.Filter import * +from EventAggregatorSupport.Formatting import * +from EventAggregatorSupport.Locations import * +from EventAggregatorSupport.Resources import * +from EventAggregatorSupport.Types import * +from EventAggregatorSupport.View import * + +__version__ = "0.10" + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r cfb2743a9f38 -r 693940d48e8b PKG-INFO --- a/PKG-INFO Thu Apr 25 23:38:40 2013 +0200 +++ b/PKG-INFO Tue Apr 30 23:49:32 2013 +0200 @@ -1,12 +1,12 @@ Metadata-Version: 1.1 Name: EventAggregator -Version: 0.9.1 +Version: 0.10 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.9.1.tar.bz2 +Download-url: http://moinmo.in/MacroMarket/EventAggregator?action=AttachFile&do=view&target=EventAggregator-0.10.tar.bz2 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 cfb2743a9f38 -r 693940d48e8b actions/EventAggregatorNewEvent.py --- a/actions/EventAggregatorNewEvent.py Thu Apr 25 23:38:40 2013 +0200 +++ b/actions/EventAggregatorNewEvent.py Tue Apr 30 23:49:32 2013 +0200 @@ -2,7 +2,7 @@ """ MoinMoin - EventAggregatorNewEvent Action - @copyright: 2008, 2009, 2010, 2011, 2012 by Paul Boddie + @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie @copyright: 2000-2004 Juergen Hermann , 2003-2008 MoinMoin:ThomasWaldmann, 2004-2006 MoinMoin:AlexanderSchremmer, @@ -10,11 +10,13 @@ @license: GNU GPL (v2 or later), see COPYING.txt for details. """ +from EventAggregatorSupport import * +from EventAggregatorSupport.Actions import ActionSupport + from MoinMoin.action import ActionBase from MoinMoin.Page import Page from MoinMoin.PageEditor import PageEditor from MoinMoin import config -from EventAggregatorSupport import * import re try: diff -r cfb2743a9f38 -r 693940d48e8b actions/EventAggregatorSummary.py --- a/actions/EventAggregatorSummary.py Thu Apr 25 23:38:40 2013 +0200 +++ b/actions/EventAggregatorSummary.py Tue Apr 30 23:49:32 2013 +0200 @@ -2,7 +2,7 @@ """ MoinMoin - EventAggregatorSummary Action - @copyright: 2008, 2009, 2010, 2011, 2012 by Paul Boddie + @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie @copyright: 2000-2004 Juergen Hermann , 2003-2008 MoinMoin:ThomasWaldmann, 2004-2006 MoinMoin:AlexanderSchremmer, @@ -11,11 +11,15 @@ @license: GNU GPL (v2 or later), see COPYING.txt for details. """ +from MoinDateSupport import * + +from EventAggregatorSupport import * +from EventAggregatorSupport.Actions import ActionSupport + from MoinMoin.action import ActionBase from MoinMoin import config from MoinMoin.Page import Page from MoinMoin import wikiutil -from EventAggregatorSupport import * Dependencies = ['pages'] diff -r cfb2743a9f38 -r 693940d48e8b actions/EventAggregatorUpdate.py --- a/actions/EventAggregatorUpdate.py Thu Apr 25 23:38:40 2013 +0200 +++ b/actions/EventAggregatorUpdate.py Tue Apr 30 23:49:32 2013 +0200 @@ -2,13 +2,14 @@ """ MoinMoin - EventAggregatorUpdate Action - @copyright: 2012 by Paul Boddie + @copyright: 2012, 2013 by Paul Boddie @license: GNU GPL (v2 or later), see COPYING.txt for details. """ +from MoinDateSupport import getParameterDate, getParameterMonth +from EventAggregatorSupport import * from MoinMoin.Page import Page from MoinMoin import config -from EventAggregatorSupport import * Dependencies = ['pages'] diff -r cfb2743a9f38 -r 693940d48e8b macros/EventAggregator.py --- a/macros/EventAggregator.py Thu Apr 25 23:38:40 2013 +0200 +++ b/macros/EventAggregator.py Tue Apr 30 23:49:32 2013 +0200 @@ -2,14 +2,17 @@ """ MoinMoin - EventAggregator Macro - @copyright: 2008, 2009, 2010, 2011, 2012 by Paul Boddie + @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie @copyright: 2000-2004 Juergen Hermann , 2005-2008 MoinMoin:ThomasWaldmann @license: GNU GPL (v2 or later), see COPYING.txt for details. """ +from EventAggregatorSupport.Resources import getEventsUsingParameters +from EventAggregatorSupport.View import View, getQualifiedParameter +from MoinDateSupport import getFormDate, getFormMonth, \ + getParameterDate, getParameterMonth from MoinMoin import wikiutil -from EventAggregatorSupport import * Dependencies = ['pages'] diff -r cfb2743a9f38 -r 693940d48e8b setup.py --- a/setup.py Thu Apr 25 23:38:40 2013 +0200 +++ b/setup.py Tue Apr 30 23:49:32 2013 +0200 @@ -8,6 +8,7 @@ author = "Paul Boddie", author_email = "paul@boddie.org.uk", url = "http://moinmo.in/MacroMarket/EventAggregator", - version = "0.9.1", - py_modules = ["EventAggregatorSupport", "MoinMoin.script.import.eventfeed"] + version = "0.10", + packages = ["EventAggregatorSupport"], + py_modules = ["MoinMoin.script.import.eventfeed"] )