EventAggregator

Change of EventAggregatorSupport.py

307:1ae42f5a82a2
EventAggregatorSupport.py
     1.1 --- a/EventAggregatorSupport.py	Mon Jun 04 21:07:26 2012 +0200
     1.2 +++ b/EventAggregatorSupport.py	Mon Jun 18 01:09:14 2012 +0200
     1.3 @@ -323,6 +323,73 @@
     1.4  
     1.5      return [Page(request, page.page_name) for page in result_pages]
     1.6  
     1.7 +# Event parsing from page texts.
     1.8 +
     1.9 +def parseEvents(text, page):
    1.10 +
    1.11 +    """
    1.12 +    Parse events in the given 'text', returning a list of event objects for the
    1.13 +    given 'page'.
    1.14 +    """
    1.15 +
    1.16 +    details = {}
    1.17 +    events = [Event(page, details)]
    1.18 +
    1.19 +    for match in definition_list_regexp.finditer(text):
    1.20 +
    1.21 +        # Skip commented-out items.
    1.22 +
    1.23 +        if match.group("optcomment"):
    1.24 +            continue
    1.25 +
    1.26 +        # Permit case-insensitive list terms.
    1.27 +
    1.28 +        term = match.group("term").lower()
    1.29 +        desc = match.group("desc")
    1.30 +
    1.31 +        # Special value type handling.
    1.32 +
    1.33 +        # Dates.
    1.34 +
    1.35 +        if term in ("start", "end"):
    1.36 +            desc = getDateTime(desc)
    1.37 +
    1.38 +        # Lists (whose elements may be quoted).
    1.39 +
    1.40 +        elif term in ("topics", "categories"):
    1.41 +            desc = map(getSimpleWikiText, to_list(desc, ","))
    1.42 +
    1.43 +        # Position details.
    1.44 +
    1.45 +        elif term == "geo":
    1.46 +            try:
    1.47 +                desc = map(getMapReference, to_list(desc, None))
    1.48 +                if len(desc) != 2:
    1.49 +                    continue
    1.50 +            except (KeyError, ValueError):
    1.51 +                continue
    1.52 +
    1.53 +        # Labels which may well be quoted.
    1.54 +
    1.55 +        elif term in ("title", "summary", "description", "location"):
    1.56 +            desc = getSimpleWikiText(desc.strip())
    1.57 +
    1.58 +        if desc is not None:
    1.59 +
    1.60 +            # Handle apparent duplicates by creating a new set of
    1.61 +            # details.
    1.62 +
    1.63 +            if details.has_key(term):
    1.64 +
    1.65 +                # Make a new event.
    1.66 +
    1.67 +                details = {}
    1.68 +                events.append(Event(page, details))
    1.69 +
    1.70 +            details[term] = desc
    1.71 +
    1.72 +    return events
    1.73 +
    1.74  # Event resources providing collections of events.
    1.75  
    1.76  class EventResource:
    1.77 @@ -535,62 +602,10 @@
    1.78          "Return a list of events from this page."
    1.79  
    1.80          if self.events is None:
    1.81 -            details = {}
    1.82 -            self.events = [Event(self, details)]
    1.83 -
    1.84              if self.getFormat() == "wiki":
    1.85 -                for match in definition_list_regexp.finditer(self.getBody()):
    1.86 -
    1.87 -                    # Skip commented-out items.
    1.88 -
    1.89 -                    if match.group("optcomment"):
    1.90 -                        continue
    1.91 -
    1.92 -                    # Permit case-insensitive list terms.
    1.93 -
    1.94 -                    term = match.group("term").lower()
    1.95 -                    desc = match.group("desc")
    1.96 -
    1.97 -                    # Special value type handling.
    1.98 -
    1.99 -                    # Dates.
   1.100 -
   1.101 -                    if term in ("start", "end"):
   1.102 -                        desc = getDateTime(desc)
   1.103 -
   1.104 -                    # Lists (whose elements may be quoted).
   1.105 -
   1.106 -                    elif term in ("topics", "categories"):
   1.107 -                        desc = map(getSimpleWikiText, to_list(desc, ","))
   1.108 -
   1.109 -                    # Position details.
   1.110 -
   1.111 -                    elif term == "geo":
   1.112 -                        try:
   1.113 -                            desc = map(getMapReference, to_list(desc, None))
   1.114 -                            if len(desc) != 2:
   1.115 -                                continue
   1.116 -                        except (KeyError, ValueError):
   1.117 -                            continue
   1.118 -
   1.119 -                    # Labels which may well be quoted.
   1.120 -
   1.121 -                    elif term in ("title", "summary", "description", "location"):
   1.122 -                        desc = getSimpleWikiText(desc.strip())
   1.123 -
   1.124 -                    if desc is not None:
   1.125 -
   1.126 -                        # Handle apparent duplicates by creating a new set of
   1.127 -                        # details.
   1.128 -
   1.129 -                        if details.has_key(term):
   1.130 -
   1.131 -                            # Make a new event.
   1.132 -
   1.133 -                            details = {}
   1.134 -                            self.events.append(Event(self, details))
   1.135 -
   1.136 -                        details[term] = desc
   1.137 +                self.events = parseEvents(self.getBody(), self)
   1.138 +            else:
   1.139 +                self.events = []
   1.140  
   1.141          return self.events
   1.142  
   1.143 @@ -1676,8 +1691,10 @@
   1.144              self.calendar_start, self.calendar_end
   1.145              )
   1.146  
   1.147 -        return "action=EventAggregatorNewEvent&%s&%s&template=%s&parent=%s&%s" % (
   1.148 -            args, self.category_name_parameters, self.template_name, self.parent_name or "",
   1.149 +        return "action=EventAggregatorNewEvent%s%s&template=%s&parent=%s&%s" % (
   1.150 +            args and "&%s" % args,
   1.151 +            self.category_name_parameters and "&%s" % self.category_name_parameters,
   1.152 +            self.template_name, self.parent_name or "",
   1.153              navigation_link)
   1.154  
   1.155      def getFullDateLabel(self, date):
   1.156 @@ -1747,11 +1764,11 @@
   1.157  
   1.158          # Generate the links.
   1.159  
   1.160 -        download_dialogue_link = "action=EventAggregatorSummary&parent=%s&resolution=%s&%s&%s" % (
   1.161 +        download_dialogue_link = "action=EventAggregatorSummary&parent=%s&resolution=%s%s%s" % (
   1.162              self.parent_name or "",
   1.163              self.resolution,
   1.164 -            self.category_name_parameters,
   1.165 -            self.remote_source_parameters
   1.166 +            self.category_name_parameters and "&%s" % self.category_name_parameters,
   1.167 +            self.remote_source_parameters and "&%s" % self.remote_source_parameters
   1.168              )
   1.169          download_all_link = download_dialogue_link + "&doit=1"
   1.170          download_link = download_all_link + ("&%s&%s" % (
   1.171 @@ -3362,4 +3379,191 @@
   1.172  </script>
   1.173  """
   1.174  
   1.175 +# Event-only formatting.
   1.176 +
   1.177 +def formatEventsForOutputType(events, request, mimetype, parent=None, descriptions=None, latest_timestamp=None):
   1.178 +
   1.179 +    """
   1.180 +    Format the given 'events' using the 'request' for the given 'mimetype'.
   1.181 +
   1.182 +    The optional 'parent' indicates the "natural" parent page of the events. Any
   1.183 +    event pages residing beneath the parent page will have their names
   1.184 +    reproduced as relative to the parent page.
   1.185 +
   1.186 +    The optional 'descriptions' indicates the nature of any description given
   1.187 +    for events in the output resource.
   1.188 +
   1.189 +    The optional 'latest_timestamp' indicates the timestamp of the latest edit
   1.190 +    of the page or event collection.
   1.191 +    """
   1.192 +
   1.193 +    # Start the collection.
   1.194 +
   1.195 +    if mimetype == "text/calendar":
   1.196 +        request.write("BEGIN:VCALENDAR\r\n")
   1.197 +        request.write("PRODID:-//MoinMoin//EventAggregatorSummary\r\n")
   1.198 +        request.write("VERSION:2.0\r\n")
   1.199 +
   1.200 +    elif mimetype == "application/rss+xml":
   1.201 +
   1.202 +        # Using the page name and the page URL in the title, link and
   1.203 +        # description.
   1.204 +
   1.205 +        path_info = getPathInfo(request)
   1.206 +
   1.207 +        request.write('<rss version="2.0">\r\n')
   1.208 +        request.write('<channel>\r\n')
   1.209 +        request.write('<title>%s</title>\r\n' % path_info[1:])
   1.210 +        request.write('<link>%s%s</link>\r\n' % (request.getBaseURL(), path_info))
   1.211 +        request.write('<description>Events published on %s%s</description>\r\n' % (request.getBaseURL(), path_info))
   1.212 +
   1.213 +        if latest_timestamp is not None:
   1.214 +            request.write('<lastBuildDate>%s</lastBuildDate>\r\n' % latest_timestamp.as_HTTP_datetime_string())
   1.215 + 
   1.216 +        # Sort the events by start date, reversed.
   1.217 +
   1.218 +        ordered_events = getOrderedEvents(events)
   1.219 +        ordered_events.reverse()
   1.220 +        events = ordered_events
   1.221 +
   1.222 +    # Output the collection one by one.
   1.223 +
   1.224 +    for event in events:
   1.225 +        formatEventForOutputType(event, request, mimetype, parent, descriptions)
   1.226 +
   1.227 +    # End the collection.
   1.228 +
   1.229 +    if mimetype == "text/calendar":
   1.230 +        request.write("END:VCALENDAR\r\n")
   1.231 +
   1.232 +    elif mimetype == "application/rss+xml":
   1.233 +        request.write('</channel>\r\n')
   1.234 +        request.write('</rss>\r\n')
   1.235 +
   1.236 +def formatEventForOutputType(event, request, mimetype, parent=None, descriptions=None):
   1.237 +
   1.238 +    """
   1.239 +    Format the given 'event' using the 'request' for the given 'mimetype'.
   1.240 +
   1.241 +    The optional 'parent' indicates the "natural" parent page of the events. Any
   1.242 +    event pages residing beneath the parent page will have their names
   1.243 +    reproduced as relative to the parent page.
   1.244 +
   1.245 +    The optional 'descriptions' indicates the nature of any description given
   1.246 +    for events in the output resource.
   1.247 +    """
   1.248 +
   1.249 +    event_details = event.getDetails()
   1.250 +
   1.251 +    if mimetype == "text/calendar":
   1.252 +
   1.253 +        # NOTE: A custom formatter making attributes for links and plain
   1.254 +        # NOTE: text for values could be employed here.
   1.255 +
   1.256 +        # Get the summary details.
   1.257 +
   1.258 +        event_summary = event.getSummary(parent)
   1.259 +        link = event.getEventURL()
   1.260 +
   1.261 +        # Output the event details.
   1.262 +
   1.263 +        request.write("BEGIN:VEVENT\r\n")
   1.264 +        request.write("UID:%s\r\n" % link)
   1.265 +        request.write("URL:%s\r\n" % link)
   1.266 +        request.write("DTSTAMP:%04d%02d%02dT%02d%02d%02dZ\r\n" % event_details["created"].as_tuple()[:6])
   1.267 +        request.write("LAST-MODIFIED:%04d%02d%02dT%02d%02d%02dZ\r\n" % event_details["last-modified"].as_tuple()[:6])
   1.268 +        request.write("SEQUENCE:%d\r\n" % event_details["sequence"])
   1.269 +
   1.270 +        start = event_details["start"]
   1.271 +        end = event_details["end"]
   1.272 +
   1.273 +        if isinstance(start, DateTime):
   1.274 +            request.write("DTSTART")
   1.275 +            write_calendar_datetime(request, start)
   1.276 +        else:
   1.277 +            request.write("DTSTART;VALUE=DATE:%04d%02d%02d\r\n" % start.as_date().as_tuple())
   1.278 +
   1.279 +        if isinstance(end, DateTime):
   1.280 +            request.write("DTEND")
   1.281 +            write_calendar_datetime(request, end)
   1.282 +        else:
   1.283 +            request.write("DTEND;VALUE=DATE:%04d%02d%02d\r\n" % end.next_day().as_date().as_tuple())
   1.284 +
   1.285 +        request.write("SUMMARY:%s\r\n" % getQuotedText(event_summary))
   1.286 +
   1.287 +        # Optional details.
   1.288 +
   1.289 +        if event_details.get("topics") or event_details.get("categories"):
   1.290 +            request.write("CATEGORIES:%s\r\n" % ",".join(
   1.291 +                [getQuotedText(topic)
   1.292 +                    for topic in event_details.get("topics") or event_details.get("categories")]
   1.293 +                ))
   1.294 +        if event_details.has_key("location"):
   1.295 +            request.write("LOCATION:%s\r\n" % getQuotedText(event_details["location"]))
   1.296 +        if event_details.has_key("geo"):
   1.297 +            request.write("GEO:%s\r\n" % getQuotedText(";".join([str(ref.to_degrees()) for ref in event_details["geo"]])))
   1.298 +
   1.299 +        request.write("END:VEVENT\r\n")
   1.300 +
   1.301 +    elif mimetype == "application/rss+xml":
   1.302 +
   1.303 +        event_page = event.getPage()
   1.304 +        event_details = event.getDetails()
   1.305 +
   1.306 +        # Get a parser and formatter for the formatting of some attributes.
   1.307 +
   1.308 +        fmt = request.html_formatter
   1.309 +
   1.310 +        # Get the summary details.
   1.311 +
   1.312 +        event_summary = event.getSummary(parent)
   1.313 +        link = event.getEventURL()
   1.314 +
   1.315 +        request.write('<item>\r\n')
   1.316 +        request.write('<title>%s</title>\r\n' % wikiutil.escape(event_summary))
   1.317 +        request.write('<link>%s</link>\r\n' % link)
   1.318 +
   1.319 +        # Write a description according to the preferred source of
   1.320 +        # descriptions.
   1.321 +
   1.322 +        if descriptions == "page":
   1.323 +            description = event_details.get("description", "")
   1.324 +        else:
   1.325 +            description = event_details["last-comment"]
   1.326 +
   1.327 +        request.write('<description>%s</description>\r\n' %
   1.328 +            fmt.text(event_page.formatText(description, fmt)))
   1.329 +
   1.330 +        for topic in event_details.get("topics") or event_details.get("categories") or []:
   1.331 +            request.write('<category>%s</category>\r\n' %
   1.332 +                fmt.text(event_page.formatText(topic, fmt)))
   1.333 +
   1.334 +        request.write('<pubDate>%s</pubDate>\r\n' % event_details["created"].as_HTTP_datetime_string())
   1.335 +        request.write('<guid>%s#%s</guid>\r\n' % (link, event_details["sequence"]))
   1.336 +        request.write('</item>\r\n')
   1.337 +
   1.338 +# iCalendar format helper functions.
   1.339 +
   1.340 +def write_calendar_datetime(request, datetime):
   1.341 +
   1.342 +    """
   1.343 +    Write to the given 'request' the 'datetime' using appropriate time zone
   1.344 +    information.
   1.345 +    """
   1.346 +
   1.347 +    utc_datetime = datetime.to_utc()
   1.348 +    if utc_datetime:
   1.349 +        request.write(";VALUE=DATE-TIME:%04d%02d%02dT%02d%02d%02dZ\r\n" % utc_datetime.padded().as_tuple()[:-1])
   1.350 +    else:
   1.351 +        zone = datetime.time_zone()
   1.352 +        if zone:
   1.353 +            request.write(";TZID=/%s" % zone)
   1.354 +        request.write(";VALUE=DATE-TIME:%04d%02d%02dT%02d%02d%02d\r\n" % datetime.padded().as_tuple()[:-1])
   1.355 +
   1.356 +def getQuotedText(text):
   1.357 +
   1.358 +    "Return the 'text' quoted for iCalendar purposes."
   1.359 +
   1.360 +    return text.replace(";", r"\;").replace(",", r"\,")
   1.361 +
   1.362  # vim: tabstop=4 expandtab shiftwidth=4